Upmerge from v1.120.x
diff --git a/.gitignore b/.gitignore
index cde82bc..7b4c5d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,6 +115,7 @@
 bazel-grpc
 bazel-out
 bazel-testlogs
+bazel_format_virtual_environment/
 
 # Debug output
 gdb.txt
@@ -134,3 +135,11 @@
 
 # cmake build files
 /cmake/build
+
+# Visual Studio Code artifacts
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
diff --git a/.gitmodules b/.gitmodules
index 06f1394..f4690a2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -50,7 +50,7 @@
 	url = https://github.com/googleapis/googleapis.git
 [submodule "third_party/protoc-gen-validate"]
 	path = third_party/protoc-gen-validate
-	url = https://github.com/lyft/protoc-gen-validate.git
+	url = https://github.com/envoyproxy/protoc-gen-validate.git
 [submodule "third_party/upb"]
 	path = third_party/upb
 	url = https://github.com/google/upb.git
diff --git a/.pylintrc b/.pylintrc
index ba74dec..fcc8e73 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -6,6 +6,8 @@
 	src/python/grpcio/grpc/framework/foundation,
 	src/python/grpcio/grpc/framework/interfaces,
 
+extension-pkg-whitelist=grpc._cython.cygrpc
+
 [VARIABLES]
 
 # TODO(https://github.com/PyCQA/pylint/issues/1345): How does the inspection
@@ -17,7 +19,7 @@
 # NOTE(nathaniel): Not particularly attached to this value; it just seems to
 # be what works for us at the moment (excepting the dead-code-walking Beta
 # API).
-max-args=6
+max-args=7
 
 [MISCELLANEOUS]
 
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 700c61c..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-    "version": "0.2.0",
-    "configurations": [
-        {
-            "type": "node",
-            "request": "launch",
-            "name": "Mocha Tests",
-            "cwd": "${workspaceRoot}",
-            "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
-            "windows": {
-                "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
-            },
-            "runtimeArgs": [
-                "-u",
-                "tdd",
-                "--timeout",
-                "999999",
-                "--colors",
-                "${workspaceRoot}/src/node/test"
-            ],
-            "internalConsoleOptions": "openOnSessionStart"
-        },
-        {
-            "type": "node",
-            "request": "attach",
-            "name": "Attach to Process",
-            "port": 5858
-        }
-    ]
-}
diff --git a/BUILD b/BUILD
index 9157f5a..5c05c9e 100644
--- a/BUILD
+++ b/BUILD
@@ -74,11 +74,11 @@
 )
 
 # This should be updated along with build.yaml
-g_stands_for = "godric"
+g_stands_for = "gandalf"
 
 core_version = "7.0.0"
 
-version = "1.20.1"
+version = "1.21.0-dev"
 
 GPR_PUBLIC_HDRS = [
     "include/grpc/support/alloc.h",
@@ -192,8 +192,8 @@
     "include/grpc++/impl/service_type.h",
     "include/grpc++/impl/sync_cxx11.h",
     "include/grpc++/impl/sync_no_cxx11.h",
-    "include/grpc++/resource_quota.h",
     "include/grpc++/security/auth_context.h",
+    "include/grpc++/resource_quota.h",
     "include/grpc++/security/auth_metadata_processor.h",
     "include/grpc++/security/credentials.h",
     "include/grpc++/security/server_credentials.h",
@@ -216,16 +216,20 @@
     "include/grpcpp/alarm.h",
     "include/grpcpp/alarm_impl.h",
     "include/grpcpp/channel.h",
+    "include/grpcpp/channel_impl.h",
     "include/grpcpp/client_context.h",
     "include/grpcpp/completion_queue.h",
     "include/grpcpp/create_channel.h",
+    "include/grpcpp/create_channel_impl.h",
     "include/grpcpp/create_channel_posix.h",
     "include/grpcpp/create_channel_posix_impl.h",
     "include/grpcpp/ext/health_check_service_server_builder_option.h",
     "include/grpcpp/generic/async_generic_service.h",
     "include/grpcpp/generic/generic_stub.h",
+    "include/grpcpp/generic/generic_stub_impl.h",
     "include/grpcpp/grpcpp.h",
     "include/grpcpp/health_check_service_interface.h",
+    "include/grpcpp/health_check_service_interface_impl.h",
     "include/grpcpp/impl/call.h",
     "include/grpcpp/impl/channel_argument_option.h",
     "include/grpcpp/impl/client_unary_call.h",
@@ -239,16 +243,23 @@
     "include/grpcpp/impl/server_builder_option_impl.h",
     "include/grpcpp/impl/server_builder_plugin.h",
     "include/grpcpp/impl/server_initializer.h",
+    "include/grpcpp/impl/server_initializer_impl.h",
     "include/grpcpp/impl/service_type.h",
     "include/grpcpp/impl/sync_cxx11.h",
     "include/grpcpp/impl/sync_no_cxx11.h",
     "include/grpcpp/resource_quota.h",
+    "include/grpcpp/resource_quota_impl.h",
     "include/grpcpp/security/auth_context.h",
     "include/grpcpp/security/auth_metadata_processor.h",
+    "include/grpcpp/security/auth_metadata_processor_impl.h",
     "include/grpcpp/security/credentials.h",
+    "include/grpcpp/security/credentials_impl.h",
     "include/grpcpp/security/server_credentials.h",
+    "include/grpcpp/security/server_credentials_impl.h",
     "include/grpcpp/server.h",
+    "include/grpcpp/server_impl.h",
     "include/grpcpp/server_builder.h",
+    "include/grpcpp/server_builder_impl.h",
     "include/grpcpp/server_context.h",
     "include/grpcpp/server_posix.h",
     "include/grpcpp/server_posix_impl.h",
@@ -256,10 +267,12 @@
     "include/grpcpp/support/async_unary_call.h",
     "include/grpcpp/support/byte_buffer.h",
     "include/grpcpp/support/channel_arguments.h",
+    "include/grpcpp/support/channel_arguments_impl.h",
     "include/grpcpp/support/client_callback.h",
     "include/grpcpp/support/client_interceptor.h",
     "include/grpcpp/support/config.h",
     "include/grpcpp/support/interceptor.h",
+    "include/grpcpp/support/message_allocator.h",
     "include/grpcpp/support/proto_buffer_reader.h",
     "include/grpcpp/support/proto_buffer_writer.h",
     "include/grpcpp/support/server_callback.h",
@@ -310,7 +323,6 @@
     public_hdrs = GRPC_PUBLIC_HDRS + GRPC_SECURE_PUBLIC_HDRS,
     standalone = True,
     deps = [
-        "grpc_cfstream",
         "grpc_common",
         "grpc_lb_policy_grpclb_secure",
         "grpc_lb_policy_xds_secure",
@@ -367,7 +379,6 @@
         "grpc++_codegen_base",
         "grpc++_codegen_base_src",
         "grpc++_codegen_proto",
-        "grpc_cfstream",
     ],
 )
 
@@ -398,6 +409,7 @@
     hdrs = [
         "include/grpc++/support/error_details.h",
         "include/grpcpp/support/error_details.h",
+        "include/grpcpp/support/error_details_impl.h",
     ],
     language = "c++",
     standalone = True,
@@ -422,6 +434,7 @@
         "src/compiler/config.h",
         "src/compiler/cpp_generator.h",
         "src/compiler/cpp_generator_helpers.h",
+        "src/compiler/cpp_plugin.h",
         "src/compiler/csharp_generator.h",
         "src/compiler/csharp_generator_helpers.h",
         "src/compiler/generator_helpers.h",
@@ -519,10 +532,20 @@
 )
 
 grpc_cc_library(
+    name = "grpc++_internal_hdrs_only",
+    hdrs = [
+        "include/grpcpp/impl/codegen/sync.h",
+    ],
+    language = "c++",
+    deps = [
+        "gpr_codegen",
+    ],
+)
+
+grpc_cc_library(
     name = "gpr_base",
     srcs = [
         "src/core/lib/gpr/alloc.cc",
-        "src/core/lib/gpr/arena.cc",
         "src/core/lib/gpr/atm.cc",
         "src/core/lib/gpr/cpu_iphone.cc",
         "src/core/lib/gpr/cpu_linux.cc",
@@ -555,7 +578,9 @@
         "src/core/lib/gpr/tmpfile_posix.cc",
         "src/core/lib/gpr/tmpfile_windows.cc",
         "src/core/lib/gpr/wrap_memcpy.cc",
+        "src/core/lib/gprpp/arena.cc",
         "src/core/lib/gprpp/fork.cc",
+        "src/core/lib/gprpp/global_config_env.cc",
         "src/core/lib/gprpp/thd_posix.cc",
         "src/core/lib/gprpp/thd_windows.cc",
         "src/core/lib/profiling/basic_timers.cc",
@@ -579,10 +604,18 @@
         "src/core/lib/gpr/tmpfile.h",
         "src/core/lib/gpr/useful.h",
         "src/core/lib/gprpp/abstract.h",
+        "src/core/lib/gprpp/arena.h",
+        "src/core/lib/gprpp/atomic.h",
         "src/core/lib/gprpp/fork.h",
+        "src/core/lib/gprpp/global_config_custom.h",
+        "src/core/lib/gprpp/global_config_env.h",
+        "src/core/lib/gprpp/global_config_generic.h",
+        "src/core/lib/gprpp/global_config.h",
         "src/core/lib/gprpp/manual_constructor.h",
+        "src/core/lib/gprpp/map.h",
         "src/core/lib/gprpp/memory.h",
-        "src/core/lib/gprpp/mutex_lock.h",
+        "src/core/lib/gprpp/pair.h",
+        "src/core/lib/gprpp/sync.h",
         "src/core/lib/gprpp/thd.h",
         "src/core/lib/profiling/timers.h",
     ],
@@ -716,6 +749,7 @@
         "src/core/lib/channel/handshaker_registry.cc",
         "src/core/lib/channel/status_util.cc",
         "src/core/lib/compression/compression.cc",
+        "src/core/lib/compression/compression_args.cc",
         "src/core/lib/compression/compression_internal.cc",
         "src/core/lib/compression/message_compress.cc",
         "src/core/lib/compression/stream_compression.cc",
@@ -728,12 +762,15 @@
         "src/core/lib/http/parser.cc",
         "src/core/lib/iomgr/buffer_list.cc",
         "src/core/lib/iomgr/call_combiner.cc",
+        "src/core/lib/iomgr/cfstream_handle.cc",
         "src/core/lib/iomgr/combiner.cc",
         "src/core/lib/iomgr/endpoint.cc",
+        "src/core/lib/iomgr/endpoint_cfstream.cc",
         "src/core/lib/iomgr/endpoint_pair_posix.cc",
         "src/core/lib/iomgr/endpoint_pair_uv.cc",
         "src/core/lib/iomgr/endpoint_pair_windows.cc",
         "src/core/lib/iomgr/error.cc",
+        "src/core/lib/iomgr/error_cfstream.cc",
         "src/core/lib/iomgr/ev_epoll1_linux.cc",
         "src/core/lib/iomgr/ev_epollex_linux.cc",
         "src/core/lib/iomgr/ev_poll_posix.cc",
@@ -754,6 +791,7 @@
         "src/core/lib/iomgr/iomgr_custom.cc",
         "src/core/lib/iomgr/iomgr_internal.cc",
         "src/core/lib/iomgr/iomgr_posix.cc",
+        "src/core/lib/iomgr/iomgr_posix_cfstream.cc",
         "src/core/lib/iomgr/iomgr_windows.cc",
         "src/core/lib/iomgr/is_epollexclusive_available.cc",
         "src/core/lib/iomgr/load_file.cc",
@@ -780,6 +818,7 @@
         "src/core/lib/iomgr/socket_utils_windows.cc",
         "src/core/lib/iomgr/socket_windows.cc",
         "src/core/lib/iomgr/tcp_client.cc",
+        "src/core/lib/iomgr/tcp_client_cfstream.cc",
         "src/core/lib/iomgr/tcp_client_custom.cc",
         "src/core/lib/iomgr/tcp_client_posix.cc",
         "src/core/lib/iomgr/tcp_client_windows.cc",
@@ -866,6 +905,7 @@
         "src/core/lib/channel/handshaker_registry.h",
         "src/core/lib/channel/status_util.h",
         "src/core/lib/compression/algorithm_metadata.h",
+        "src/core/lib/compression/compression_args.h",
         "src/core/lib/compression/compression_internal.h",
         "src/core/lib/compression/message_compress.h",
         "src/core/lib/compression/stream_compression.h",
@@ -879,12 +919,15 @@
         "src/core/lib/iomgr/block_annotate.h",
         "src/core/lib/iomgr/buffer_list.h",
         "src/core/lib/iomgr/call_combiner.h",
+        "src/core/lib/iomgr/cfstream_handle.h",
         "src/core/lib/iomgr/closure.h",
         "src/core/lib/iomgr/combiner.h",
         "src/core/lib/iomgr/dynamic_annotations.h",
         "src/core/lib/iomgr/endpoint.h",
+        "src/core/lib/iomgr/endpoint_cfstream.h",
         "src/core/lib/iomgr/endpoint_pair.h",
         "src/core/lib/iomgr/error.h",
+        "src/core/lib/iomgr/error_cfstream.h",
         "src/core/lib/iomgr/error_internal.h",
         "src/core/lib/iomgr/ev_epoll1_linux.h",
         "src/core/lib/iomgr/ev_epollex_linux.h",
@@ -1040,27 +1083,6 @@
 )
 
 grpc_cc_library(
-    name = "grpc_cfstream",
-    srcs = [
-        "src/core/lib/iomgr/cfstream_handle.cc",
-        "src/core/lib/iomgr/endpoint_cfstream.cc",
-        "src/core/lib/iomgr/error_cfstream.cc",
-        "src/core/lib/iomgr/iomgr_posix_cfstream.cc",
-        "src/core/lib/iomgr/tcp_client_cfstream.cc",
-    ],
-    hdrs = [
-        "src/core/lib/iomgr/cfstream_handle.h",
-        "src/core/lib/iomgr/endpoint_cfstream.h",
-        "src/core/lib/iomgr/error_cfstream.h",
-    ],
-    use_cfstream = True,
-    deps = [
-        ":gpr_base",
-        ":grpc_base",
-    ],
-)
-
-grpc_cc_library(
     name = "grpc_client_channel",
     srcs = [
         "src/core/ext/filters/client_channel/backup_poller.cc",
@@ -1479,6 +1501,7 @@
     language = "c++",
     public_hdrs = [
         "include/grpcpp/ext/server_load_reporting.h",
+        "include/grpcpp/ext/server_load_reporting_impl.h",
     ],
     deps = [
         "lb_server_load_reporting_filter",
@@ -1539,6 +1562,20 @@
 )
 
 grpc_cc_library(
+    name = "grpc_resolver_dns_selection",
+    srcs = [
+        "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc",
+    ],
+    hdrs = [
+        "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h",
+    ],
+    language = "c++",
+    deps = [
+        "grpc_base",
+    ],
+)
+
+grpc_cc_library(
     name = "grpc_resolver_dns_native",
     srcs = [
         "src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc",
@@ -1547,6 +1584,7 @@
     deps = [
         "grpc_base",
         "grpc_client_channel",
+        "grpc_resolver_dns_selection",
     ],
 )
 
@@ -1555,16 +1593,20 @@
     srcs = [
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc",
     ],
     hdrs = [
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h",
     ],
     external_deps = [
         "cares",
@@ -1574,6 +1616,7 @@
     deps = [
         "grpc_base",
         "grpc_client_channel",
+        "grpc_resolver_dns_selection",
     ],
 )
 
@@ -2120,6 +2163,7 @@
         "include/grpcpp/impl/codegen/client_interceptor.h",
         "include/grpcpp/impl/codegen/client_unary_call.h",
         "include/grpcpp/impl/codegen/completion_queue.h",
+        "include/grpcpp/impl/codegen/completion_queue_impl.h",
         "include/grpcpp/impl/codegen/completion_queue_tag.h",
         "include/grpcpp/impl/codegen/config.h",
         "include/grpcpp/impl/codegen/core_codegen_interface.h",
@@ -2128,6 +2172,7 @@
         "include/grpcpp/impl/codegen/intercepted_channel.h",
         "include/grpcpp/impl/codegen/interceptor.h",
         "include/grpcpp/impl/codegen/interceptor_common.h",
+        "include/grpcpp/impl/codegen/message_allocator.h",
         "include/grpcpp/impl/codegen/metadata_map.h",
         "include/grpcpp/impl/codegen/method_handler_impl.h",
         "include/grpcpp/impl/codegen/rpc_method.h",
@@ -2148,6 +2193,7 @@
         "include/grpcpp/impl/codegen/time.h",
     ],
     deps = [
+        "grpc++_internal_hdrs_only",
         "grpc_codegen",
     ],
 )
@@ -2203,6 +2249,7 @@
     public_hdrs = [
         "include/grpc++/ext/proto_server_reflection_plugin.h",
         "include/grpcpp/ext/proto_server_reflection_plugin.h",
+        "include/grpcpp/ext/proto_server_reflection_plugin_impl.h",
     ],
     deps = [
         ":grpc++",
@@ -2319,6 +2366,28 @@
 
 #TODO: Get this into build.yaml once we start using it.
 grpc_cc_library(
+    name = "envoy_lrs_upb",
+    srcs = [
+        "src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.c",
+        "src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.c",
+    ],
+    hdrs = [
+        "src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.h",
+        "src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.h",
+    ],
+    language = "c++",
+    external_deps = [
+        "upb_lib",
+    ],
+    deps = [
+        ":envoy_core_upb",
+        ":google_api_upb",
+        ":proto_gen_validate_upb",
+    ],
+    tags = ["no_windows"],
+)
+
+grpc_cc_library(
     name = "envoy_ads_upb",
     srcs = [
         "src/core/ext/upb-generated/envoy/api/v2/auth/cert.upb.c",
@@ -2350,6 +2419,7 @@
         ":google_api_upb",
         ":proto_gen_validate_upb",
     ],
+    tags = ["no_windows"],
 )
 
 grpc_cc_library(
@@ -2457,3 +2527,11 @@
 )
 
 grpc_generate_one_off_targets()
+
+filegroup(
+    name = "root_certificates",
+    srcs = [
+        "etc/roots.pem",
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/BUILD.gn b/BUILD.gn
index e566a01..47b2747 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -131,7 +131,6 @@
         "include/grpc/support/time.h",
         "src/core/lib/gpr/alloc.cc",
         "src/core/lib/gpr/alloc.h",
-        "src/core/lib/gpr/arena.cc",
         "src/core/lib/gpr/arena.h",
         "src/core/lib/gpr/atm.cc",
         "src/core/lib/gpr/cpu_iphone.cc",
@@ -180,12 +179,21 @@
         "src/core/lib/gpr/useful.h",
         "src/core/lib/gpr/wrap_memcpy.cc",
         "src/core/lib/gprpp/abstract.h",
+        "src/core/lib/gprpp/arena.cc",
+        "src/core/lib/gprpp/arena.h",
         "src/core/lib/gprpp/atomic.h",
         "src/core/lib/gprpp/fork.cc",
         "src/core/lib/gprpp/fork.h",
+        "src/core/lib/gprpp/global_config.h",
+        "src/core/lib/gprpp/global_config_custom.h",
+        "src/core/lib/gprpp/global_config_env.cc",
+        "src/core/lib/gprpp/global_config_env.h",
+        "src/core/lib/gprpp/global_config_generic.h",
         "src/core/lib/gprpp/manual_constructor.h",
+        "src/core/lib/gprpp/map.h",
         "src/core/lib/gprpp/memory.h",
-        "src/core/lib/gprpp/mutex_lock.h",
+        "src/core/lib/gprpp/pair.h",
+        "src/core/lib/gprpp/sync.h",
         "src/core/lib/gprpp/thd.h",
         "src/core/lib/gprpp/thd_posix.cc",
         "src/core/lib/gprpp/thd_windows.cc",
@@ -307,13 +315,19 @@
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc",
+        "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h",
         "src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc",
         "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc",
         "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h",
@@ -443,6 +457,8 @@
         "src/core/lib/channel/status_util.h",
         "src/core/lib/compression/algorithm_metadata.h",
         "src/core/lib/compression/compression.cc",
+        "src/core/lib/compression/compression_args.cc",
+        "src/core/lib/compression/compression_args.h",
         "src/core/lib/compression/compression_internal.cc",
         "src/core/lib/compression/compression_internal.h",
         "src/core/lib/compression/message_compress.cc",
@@ -477,18 +493,24 @@
         "src/core/lib/iomgr/buffer_list.h",
         "src/core/lib/iomgr/call_combiner.cc",
         "src/core/lib/iomgr/call_combiner.h",
+        "src/core/lib/iomgr/cfstream_handle.cc",
+        "src/core/lib/iomgr/cfstream_handle.h",
         "src/core/lib/iomgr/closure.h",
         "src/core/lib/iomgr/combiner.cc",
         "src/core/lib/iomgr/combiner.h",
         "src/core/lib/iomgr/dynamic_annotations.h",
         "src/core/lib/iomgr/endpoint.cc",
         "src/core/lib/iomgr/endpoint.h",
+        "src/core/lib/iomgr/endpoint_cfstream.cc",
+        "src/core/lib/iomgr/endpoint_cfstream.h",
         "src/core/lib/iomgr/endpoint_pair.h",
         "src/core/lib/iomgr/endpoint_pair_posix.cc",
         "src/core/lib/iomgr/endpoint_pair_uv.cc",
         "src/core/lib/iomgr/endpoint_pair_windows.cc",
         "src/core/lib/iomgr/error.cc",
         "src/core/lib/iomgr/error.h",
+        "src/core/lib/iomgr/error_cfstream.cc",
+        "src/core/lib/iomgr/error_cfstream.h",
         "src/core/lib/iomgr/error_internal.h",
         "src/core/lib/iomgr/ev_epoll1_linux.cc",
         "src/core/lib/iomgr/ev_epoll1_linux.h",
@@ -524,6 +546,7 @@
         "src/core/lib/iomgr/iomgr_internal.h",
         "src/core/lib/iomgr/iomgr_posix.cc",
         "src/core/lib/iomgr/iomgr_posix.h",
+        "src/core/lib/iomgr/iomgr_posix_cfstream.cc",
         "src/core/lib/iomgr/iomgr_uv.cc",
         "src/core/lib/iomgr/iomgr_windows.cc",
         "src/core/lib/iomgr/is_epollexclusive_available.cc",
@@ -579,6 +602,7 @@
         "src/core/lib/iomgr/sys_epoll_wrapper.h",
         "src/core/lib/iomgr/tcp_client.cc",
         "src/core/lib/iomgr/tcp_client.h",
+        "src/core/lib/iomgr/tcp_client_cfstream.cc",
         "src/core/lib/iomgr/tcp_client_custom.cc",
         "src/core/lib/iomgr/tcp_client_posix.cc",
         "src/core/lib/iomgr/tcp_client_posix.h",
@@ -1001,16 +1025,20 @@
         "include/grpcpp/alarm.h",
         "include/grpcpp/alarm_impl.h",
         "include/grpcpp/channel.h",
+        "include/grpcpp/channel_impl.h",
         "include/grpcpp/client_context.h",
         "include/grpcpp/completion_queue.h",
         "include/grpcpp/create_channel.h",
+        "include/grpcpp/create_channel_impl.h",
         "include/grpcpp/create_channel_posix.h",
         "include/grpcpp/create_channel_posix_impl.h",
         "include/grpcpp/ext/health_check_service_server_builder_option.h",
         "include/grpcpp/generic/async_generic_service.h",
         "include/grpcpp/generic/generic_stub.h",
+        "include/grpcpp/generic/generic_stub_impl.h",
         "include/grpcpp/grpcpp.h",
         "include/grpcpp/health_check_service_interface.h",
+        "include/grpcpp/health_check_service_interface_impl.h",
         "include/grpcpp/impl/call.h",
         "include/grpcpp/impl/channel_argument_option.h",
         "include/grpcpp/impl/client_unary_call.h",
@@ -1029,6 +1057,7 @@
         "include/grpcpp/impl/codegen/client_interceptor.h",
         "include/grpcpp/impl/codegen/client_unary_call.h",
         "include/grpcpp/impl/codegen/completion_queue.h",
+        "include/grpcpp/impl/codegen/completion_queue_impl.h",
         "include/grpcpp/impl/codegen/completion_queue_tag.h",
         "include/grpcpp/impl/codegen/config.h",
         "include/grpcpp/impl/codegen/config_protobuf.h",
@@ -1040,6 +1069,7 @@
         "include/grpcpp/impl/codegen/intercepted_channel.h",
         "include/grpcpp/impl/codegen/interceptor.h",
         "include/grpcpp/impl/codegen/interceptor_common.h",
+        "include/grpcpp/impl/codegen/message_allocator.h",
         "include/grpcpp/impl/codegen/metadata_map.h",
         "include/grpcpp/impl/codegen/method_handler_impl.h",
         "include/grpcpp/impl/codegen/proto_buffer_reader.h",
@@ -1059,6 +1089,7 @@
         "include/grpcpp/impl/codegen/status_code_enum.h",
         "include/grpcpp/impl/codegen/string_ref.h",
         "include/grpcpp/impl/codegen/stub_options.h",
+        "include/grpcpp/impl/codegen/sync.h",
         "include/grpcpp/impl/codegen/sync_stream.h",
         "include/grpcpp/impl/codegen/time.h",
         "include/grpcpp/impl/grpc_library.h",
@@ -1070,25 +1101,34 @@
         "include/grpcpp/impl/server_builder_option_impl.h",
         "include/grpcpp/impl/server_builder_plugin.h",
         "include/grpcpp/impl/server_initializer.h",
+        "include/grpcpp/impl/server_initializer_impl.h",
         "include/grpcpp/impl/service_type.h",
         "include/grpcpp/resource_quota.h",
+        "include/grpcpp/resource_quota_impl.h",
         "include/grpcpp/security/auth_context.h",
         "include/grpcpp/security/auth_metadata_processor.h",
+        "include/grpcpp/security/auth_metadata_processor_impl.h",
         "include/grpcpp/security/credentials.h",
+        "include/grpcpp/security/credentials_impl.h",
         "include/grpcpp/security/server_credentials.h",
+        "include/grpcpp/security/server_credentials_impl.h",
         "include/grpcpp/server.h",
         "include/grpcpp/server_builder.h",
+        "include/grpcpp/server_builder_impl.h",
         "include/grpcpp/server_context.h",
+        "include/grpcpp/server_impl.h",
         "include/grpcpp/server_posix.h",
         "include/grpcpp/server_posix_impl.h",
         "include/grpcpp/support/async_stream.h",
         "include/grpcpp/support/async_unary_call.h",
         "include/grpcpp/support/byte_buffer.h",
         "include/grpcpp/support/channel_arguments.h",
+        "include/grpcpp/support/channel_arguments_impl.h",
         "include/grpcpp/support/client_callback.h",
         "include/grpcpp/support/client_interceptor.h",
         "include/grpcpp/support/config.h",
         "include/grpcpp/support/interceptor.h",
+        "include/grpcpp/support/message_allocator.h",
         "include/grpcpp/support/proto_buffer_reader.h",
         "include/grpcpp/support/proto_buffer_writer.h",
         "include/grpcpp/support/server_callback.h",
@@ -1116,6 +1156,7 @@
         "src/core/lib/channel/handshaker_registry.h",
         "src/core/lib/channel/status_util.h",
         "src/core/lib/compression/algorithm_metadata.h",
+        "src/core/lib/compression/compression_args.h",
         "src/core/lib/compression/compression_internal.h",
         "src/core/lib/compression/message_compress.h",
         "src/core/lib/compression/stream_compression.h",
@@ -1141,17 +1182,24 @@
         "src/core/lib/gpr/tmpfile.h",
         "src/core/lib/gpr/useful.h",
         "src/core/lib/gprpp/abstract.h",
+        "src/core/lib/gprpp/arena.h",
         "src/core/lib/gprpp/atomic.h",
         "src/core/lib/gprpp/debug_location.h",
         "src/core/lib/gprpp/fork.h",
+        "src/core/lib/gprpp/global_config.h",
+        "src/core/lib/gprpp/global_config_custom.h",
+        "src/core/lib/gprpp/global_config_env.h",
+        "src/core/lib/gprpp/global_config_generic.h",
         "src/core/lib/gprpp/inlined_vector.h",
         "src/core/lib/gprpp/manual_constructor.h",
+        "src/core/lib/gprpp/map.h",
         "src/core/lib/gprpp/memory.h",
-        "src/core/lib/gprpp/mutex_lock.h",
         "src/core/lib/gprpp/optional.h",
         "src/core/lib/gprpp/orphanable.h",
+        "src/core/lib/gprpp/pair.h",
         "src/core/lib/gprpp/ref_counted.h",
         "src/core/lib/gprpp/ref_counted_ptr.h",
+        "src/core/lib/gprpp/sync.h",
         "src/core/lib/gprpp/thd.h",
         "src/core/lib/http/format_request.h",
         "src/core/lib/http/httpcli.h",
@@ -1159,12 +1207,15 @@
         "src/core/lib/iomgr/block_annotate.h",
         "src/core/lib/iomgr/buffer_list.h",
         "src/core/lib/iomgr/call_combiner.h",
+        "src/core/lib/iomgr/cfstream_handle.h",
         "src/core/lib/iomgr/closure.h",
         "src/core/lib/iomgr/combiner.h",
         "src/core/lib/iomgr/dynamic_annotations.h",
         "src/core/lib/iomgr/endpoint.h",
+        "src/core/lib/iomgr/endpoint_cfstream.h",
         "src/core/lib/iomgr/endpoint_pair.h",
         "src/core/lib/iomgr/error.h",
+        "src/core/lib/iomgr/error_cfstream.h",
         "src/core/lib/iomgr/error_internal.h",
         "src/core/lib/iomgr/ev_epoll1_linux.h",
         "src/core/lib/iomgr/ev_epollex_linux.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e820737..accc288 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@
 cmake_minimum_required(VERSION 2.8)
 
 set(PACKAGE_NAME      "grpc")
-set(PACKAGE_VERSION   "1.20.1")
+set(PACKAGE_VERSION   "1.21.0-dev")
 set(PACKAGE_STRING    "${PACKAGE_NAME} ${PACKAGE_VERSION}")
 set(PACKAGE_TARNAME   "${PACKAGE_NAME}-${PACKAGE_VERSION}")
 set(PACKAGE_BUGREPORT "https://github.com/grpc/grpc/issues/")
@@ -484,6 +484,7 @@
 add_dependencies(buildtests_c h2_sockpair_1byte_test)
 add_dependencies(buildtests_c h2_spiffe_test)
 add_dependencies(buildtests_c h2_ssl_test)
+add_dependencies(buildtests_c h2_ssl_cred_reload_test)
 add_dependencies(buildtests_c h2_ssl_proxy_test)
 if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
 add_dependencies(buildtests_c h2_uds_test)
@@ -628,9 +629,12 @@
 add_dependencies(buildtests_cxx exception_test)
 add_dependencies(buildtests_cxx filter_end2end_test)
 add_dependencies(buildtests_cxx generic_end2end_test)
+add_dependencies(buildtests_cxx global_config_env_test)
+add_dependencies(buildtests_cxx global_config_test)
 add_dependencies(buildtests_cxx golden_file_test)
 add_dependencies(buildtests_cxx grpc_alts_credentials_options_test)
 add_dependencies(buildtests_cxx grpc_cli)
+add_dependencies(buildtests_cxx grpc_core_map_test)
 add_dependencies(buildtests_cxx grpc_linux_system_roots_test)
 add_dependencies(buildtests_cxx grpc_tool_test)
 add_dependencies(buildtests_cxx grpclb_api_test)
@@ -659,6 +663,7 @@
 add_dependencies(buildtests_cxx json_run_localhost)
 endif()
 add_dependencies(buildtests_cxx memory_test)
+add_dependencies(buildtests_cxx message_allocator_end2end_test)
 add_dependencies(buildtests_cxx metrics_client)
 add_dependencies(buildtests_cxx mock_test)
 add_dependencies(buildtests_cxx nonblocking_test)
@@ -698,6 +703,8 @@
 add_dependencies(buildtests_cxx server_early_return_test)
 add_dependencies(buildtests_cxx server_interceptors_end2end_test)
 add_dependencies(buildtests_cxx server_request_call_test)
+add_dependencies(buildtests_cxx service_config_end2end_test)
+add_dependencies(buildtests_cxx service_config_test)
 add_dependencies(buildtests_cxx shutdown_test)
 add_dependencies(buildtests_cxx slice_hash_table_test)
 add_dependencies(buildtests_cxx slice_weak_hash_table_test)
@@ -835,7 +842,6 @@
 
 add_library(gpr
   src/core/lib/gpr/alloc.cc
-  src/core/lib/gpr/arena.cc
   src/core/lib/gpr/atm.cc
   src/core/lib/gpr/cpu_iphone.cc
   src/core/lib/gpr/cpu_linux.cc
@@ -868,7 +874,9 @@
   src/core/lib/gpr/tmpfile_posix.cc
   src/core/lib/gpr/tmpfile_windows.cc
   src/core/lib/gpr/wrap_memcpy.cc
+  src/core/lib/gprpp/arena.cc
   src/core/lib/gprpp/fork.cc
+  src/core/lib/gprpp/global_config_env.cc
   src/core/lib/gprpp/thd_posix.cc
   src/core/lib/gprpp/thd_windows.cc
   src/core/lib/profiling/basic_timers.cc
@@ -980,6 +988,7 @@
   src/core/lib/channel/handshaker_registry.cc
   src/core/lib/channel/status_util.cc
   src/core/lib/compression/compression.cc
+  src/core/lib/compression/compression_args.cc
   src/core/lib/compression/compression_internal.cc
   src/core/lib/compression/message_compress.cc
   src/core/lib/compression/stream_compression.cc
@@ -992,12 +1001,15 @@
   src/core/lib/http/parser.cc
   src/core/lib/iomgr/buffer_list.cc
   src/core/lib/iomgr/call_combiner.cc
+  src/core/lib/iomgr/cfstream_handle.cc
   src/core/lib/iomgr/combiner.cc
   src/core/lib/iomgr/endpoint.cc
+  src/core/lib/iomgr/endpoint_cfstream.cc
   src/core/lib/iomgr/endpoint_pair_posix.cc
   src/core/lib/iomgr/endpoint_pair_uv.cc
   src/core/lib/iomgr/endpoint_pair_windows.cc
   src/core/lib/iomgr/error.cc
+  src/core/lib/iomgr/error_cfstream.cc
   src/core/lib/iomgr/ev_epoll1_linux.cc
   src/core/lib/iomgr/ev_epollex_linux.cc
   src/core/lib/iomgr/ev_poll_posix.cc
@@ -1018,6 +1030,7 @@
   src/core/lib/iomgr/iomgr_custom.cc
   src/core/lib/iomgr/iomgr_internal.cc
   src/core/lib/iomgr/iomgr_posix.cc
+  src/core/lib/iomgr/iomgr_posix_cfstream.cc
   src/core/lib/iomgr/iomgr_uv.cc
   src/core/lib/iomgr/iomgr_windows.cc
   src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -1046,6 +1059,7 @@
   src/core/lib/iomgr/socket_utils_windows.cc
   src/core/lib/iomgr/socket_windows.cc
   src/core/lib/iomgr/tcp_client.cc
+  src/core/lib/iomgr/tcp_client_cfstream.cc
   src/core/lib/iomgr/tcp_client_custom.cc
   src/core/lib/iomgr/tcp_client_posix.cc
   src/core/lib/iomgr/tcp_client_windows.cc
@@ -1279,12 +1293,16 @@
   src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc
+  src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc
   src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
   src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc
   src/core/ext/filters/census/grpc_context.cc
@@ -1406,6 +1424,7 @@
   src/core/lib/channel/handshaker_registry.cc
   src/core/lib/channel/status_util.cc
   src/core/lib/compression/compression.cc
+  src/core/lib/compression/compression_args.cc
   src/core/lib/compression/compression_internal.cc
   src/core/lib/compression/message_compress.cc
   src/core/lib/compression/stream_compression.cc
@@ -1418,12 +1437,15 @@
   src/core/lib/http/parser.cc
   src/core/lib/iomgr/buffer_list.cc
   src/core/lib/iomgr/call_combiner.cc
+  src/core/lib/iomgr/cfstream_handle.cc
   src/core/lib/iomgr/combiner.cc
   src/core/lib/iomgr/endpoint.cc
+  src/core/lib/iomgr/endpoint_cfstream.cc
   src/core/lib/iomgr/endpoint_pair_posix.cc
   src/core/lib/iomgr/endpoint_pair_uv.cc
   src/core/lib/iomgr/endpoint_pair_windows.cc
   src/core/lib/iomgr/error.cc
+  src/core/lib/iomgr/error_cfstream.cc
   src/core/lib/iomgr/ev_epoll1_linux.cc
   src/core/lib/iomgr/ev_epollex_linux.cc
   src/core/lib/iomgr/ev_poll_posix.cc
@@ -1444,6 +1466,7 @@
   src/core/lib/iomgr/iomgr_custom.cc
   src/core/lib/iomgr/iomgr_internal.cc
   src/core/lib/iomgr/iomgr_posix.cc
+  src/core/lib/iomgr/iomgr_posix_cfstream.cc
   src/core/lib/iomgr/iomgr_uv.cc
   src/core/lib/iomgr/iomgr_windows.cc
   src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -1472,6 +1495,7 @@
   src/core/lib/iomgr/socket_utils_windows.cc
   src/core/lib/iomgr/socket_windows.cc
   src/core/lib/iomgr/tcp_client.cc
+  src/core/lib/iomgr/tcp_client_cfstream.cc
   src/core/lib/iomgr/tcp_client_custom.cc
   src/core/lib/iomgr/tcp_client_posix.cc
   src/core/lib/iomgr/tcp_client_windows.cc
@@ -1817,6 +1841,7 @@
   src/core/lib/channel/handshaker_registry.cc
   src/core/lib/channel/status_util.cc
   src/core/lib/compression/compression.cc
+  src/core/lib/compression/compression_args.cc
   src/core/lib/compression/compression_internal.cc
   src/core/lib/compression/message_compress.cc
   src/core/lib/compression/stream_compression.cc
@@ -1829,12 +1854,15 @@
   src/core/lib/http/parser.cc
   src/core/lib/iomgr/buffer_list.cc
   src/core/lib/iomgr/call_combiner.cc
+  src/core/lib/iomgr/cfstream_handle.cc
   src/core/lib/iomgr/combiner.cc
   src/core/lib/iomgr/endpoint.cc
+  src/core/lib/iomgr/endpoint_cfstream.cc
   src/core/lib/iomgr/endpoint_pair_posix.cc
   src/core/lib/iomgr/endpoint_pair_uv.cc
   src/core/lib/iomgr/endpoint_pair_windows.cc
   src/core/lib/iomgr/error.cc
+  src/core/lib/iomgr/error_cfstream.cc
   src/core/lib/iomgr/ev_epoll1_linux.cc
   src/core/lib/iomgr/ev_epollex_linux.cc
   src/core/lib/iomgr/ev_poll_posix.cc
@@ -1855,6 +1883,7 @@
   src/core/lib/iomgr/iomgr_custom.cc
   src/core/lib/iomgr/iomgr_internal.cc
   src/core/lib/iomgr/iomgr_posix.cc
+  src/core/lib/iomgr/iomgr_posix_cfstream.cc
   src/core/lib/iomgr/iomgr_uv.cc
   src/core/lib/iomgr/iomgr_windows.cc
   src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -1883,6 +1912,7 @@
   src/core/lib/iomgr/socket_utils_windows.cc
   src/core/lib/iomgr/socket_windows.cc
   src/core/lib/iomgr/tcp_client.cc
+  src/core/lib/iomgr/tcp_client_cfstream.cc
   src/core/lib/iomgr/tcp_client_custom.cc
   src/core/lib/iomgr/tcp_client_posix.cc
   src/core/lib/iomgr/tcp_client_windows.cc
@@ -2141,6 +2171,7 @@
   src/core/lib/channel/handshaker_registry.cc
   src/core/lib/channel/status_util.cc
   src/core/lib/compression/compression.cc
+  src/core/lib/compression/compression_args.cc
   src/core/lib/compression/compression_internal.cc
   src/core/lib/compression/message_compress.cc
   src/core/lib/compression/stream_compression.cc
@@ -2153,12 +2184,15 @@
   src/core/lib/http/parser.cc
   src/core/lib/iomgr/buffer_list.cc
   src/core/lib/iomgr/call_combiner.cc
+  src/core/lib/iomgr/cfstream_handle.cc
   src/core/lib/iomgr/combiner.cc
   src/core/lib/iomgr/endpoint.cc
+  src/core/lib/iomgr/endpoint_cfstream.cc
   src/core/lib/iomgr/endpoint_pair_posix.cc
   src/core/lib/iomgr/endpoint_pair_uv.cc
   src/core/lib/iomgr/endpoint_pair_windows.cc
   src/core/lib/iomgr/error.cc
+  src/core/lib/iomgr/error_cfstream.cc
   src/core/lib/iomgr/ev_epoll1_linux.cc
   src/core/lib/iomgr/ev_epollex_linux.cc
   src/core/lib/iomgr/ev_poll_posix.cc
@@ -2179,6 +2213,7 @@
   src/core/lib/iomgr/iomgr_custom.cc
   src/core/lib/iomgr/iomgr_internal.cc
   src/core/lib/iomgr/iomgr_posix.cc
+  src/core/lib/iomgr/iomgr_posix_cfstream.cc
   src/core/lib/iomgr/iomgr_uv.cc
   src/core/lib/iomgr/iomgr_windows.cc
   src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -2207,6 +2242,7 @@
   src/core/lib/iomgr/socket_utils_windows.cc
   src/core/lib/iomgr/socket_windows.cc
   src/core/lib/iomgr/tcp_client.cc
+  src/core/lib/iomgr/tcp_client_cfstream.cc
   src/core/lib/iomgr/tcp_client_custom.cc
   src/core/lib/iomgr/tcp_client_posix.cc
   src/core/lib/iomgr/tcp_client_windows.cc
@@ -2441,6 +2477,7 @@
   src/core/lib/channel/handshaker_registry.cc
   src/core/lib/channel/status_util.cc
   src/core/lib/compression/compression.cc
+  src/core/lib/compression/compression_args.cc
   src/core/lib/compression/compression_internal.cc
   src/core/lib/compression/message_compress.cc
   src/core/lib/compression/stream_compression.cc
@@ -2453,12 +2490,15 @@
   src/core/lib/http/parser.cc
   src/core/lib/iomgr/buffer_list.cc
   src/core/lib/iomgr/call_combiner.cc
+  src/core/lib/iomgr/cfstream_handle.cc
   src/core/lib/iomgr/combiner.cc
   src/core/lib/iomgr/endpoint.cc
+  src/core/lib/iomgr/endpoint_cfstream.cc
   src/core/lib/iomgr/endpoint_pair_posix.cc
   src/core/lib/iomgr/endpoint_pair_uv.cc
   src/core/lib/iomgr/endpoint_pair_windows.cc
   src/core/lib/iomgr/error.cc
+  src/core/lib/iomgr/error_cfstream.cc
   src/core/lib/iomgr/ev_epoll1_linux.cc
   src/core/lib/iomgr/ev_epollex_linux.cc
   src/core/lib/iomgr/ev_poll_posix.cc
@@ -2479,6 +2519,7 @@
   src/core/lib/iomgr/iomgr_custom.cc
   src/core/lib/iomgr/iomgr_internal.cc
   src/core/lib/iomgr/iomgr_posix.cc
+  src/core/lib/iomgr/iomgr_posix_cfstream.cc
   src/core/lib/iomgr/iomgr_uv.cc
   src/core/lib/iomgr/iomgr_windows.cc
   src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -2507,6 +2548,7 @@
   src/core/lib/iomgr/socket_utils_windows.cc
   src/core/lib/iomgr/socket_windows.cc
   src/core/lib/iomgr/tcp_client.cc
+  src/core/lib/iomgr/tcp_client_cfstream.cc
   src/core/lib/iomgr/tcp_client_custom.cc
   src/core/lib/iomgr/tcp_client_posix.cc
   src/core/lib/iomgr/tcp_client_windows.cc
@@ -2648,12 +2690,16 @@
   src/core/ext/transport/inproc/inproc_transport.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc
+  src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc
   src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
   src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc
   src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc
@@ -2863,6 +2909,49 @@
 
 
 endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
+add_library(dns_test_util
+  test/cpp/naming/dns_test_util.cc
+)
+
+if(WIN32 AND MSVC)
+  set_target_properties(dns_test_util PROPERTIES COMPILE_PDB_NAME "dns_test_util"
+    COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
+  )
+  if (gRPC_INSTALL)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dns_test_util.pdb
+      DESTINATION ${gRPC_INSTALL_LIBDIR} OPTIONAL
+    )
+  endif()
+endif()
+
+
+target_include_directories(dns_test_util
+  PUBLIC $<INSTALL_INTERFACE:${gRPC_INSTALL_INCLUDEDIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+target_link_libraries(dns_test_util
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
+endif (gRPC_BUILD_TESTS)
 
 add_library(grpc++
   src/cpp/client/insecure_credentials.cc
@@ -2996,16 +3085,20 @@
   include/grpcpp/alarm.h
   include/grpcpp/alarm_impl.h
   include/grpcpp/channel.h
+  include/grpcpp/channel_impl.h
   include/grpcpp/client_context.h
   include/grpcpp/completion_queue.h
   include/grpcpp/create_channel.h
+  include/grpcpp/create_channel_impl.h
   include/grpcpp/create_channel_posix.h
   include/grpcpp/create_channel_posix_impl.h
   include/grpcpp/ext/health_check_service_server_builder_option.h
   include/grpcpp/generic/async_generic_service.h
   include/grpcpp/generic/generic_stub.h
+  include/grpcpp/generic/generic_stub_impl.h
   include/grpcpp/grpcpp.h
   include/grpcpp/health_check_service_interface.h
+  include/grpcpp/health_check_service_interface_impl.h
   include/grpcpp/impl/call.h
   include/grpcpp/impl/channel_argument_option.h
   include/grpcpp/impl/client_unary_call.h
@@ -3019,25 +3112,34 @@
   include/grpcpp/impl/server_builder_option_impl.h
   include/grpcpp/impl/server_builder_plugin.h
   include/grpcpp/impl/server_initializer.h
+  include/grpcpp/impl/server_initializer_impl.h
   include/grpcpp/impl/service_type.h
   include/grpcpp/resource_quota.h
+  include/grpcpp/resource_quota_impl.h
   include/grpcpp/security/auth_context.h
   include/grpcpp/security/auth_metadata_processor.h
+  include/grpcpp/security/auth_metadata_processor_impl.h
   include/grpcpp/security/credentials.h
+  include/grpcpp/security/credentials_impl.h
   include/grpcpp/security/server_credentials.h
+  include/grpcpp/security/server_credentials_impl.h
   include/grpcpp/server.h
   include/grpcpp/server_builder.h
+  include/grpcpp/server_builder_impl.h
   include/grpcpp/server_context.h
+  include/grpcpp/server_impl.h
   include/grpcpp/server_posix.h
   include/grpcpp/server_posix_impl.h
   include/grpcpp/support/async_stream.h
   include/grpcpp/support/async_unary_call.h
   include/grpcpp/support/byte_buffer.h
   include/grpcpp/support/channel_arguments.h
+  include/grpcpp/support/channel_arguments_impl.h
   include/grpcpp/support/client_callback.h
   include/grpcpp/support/client_interceptor.h
   include/grpcpp/support/config.h
   include/grpcpp/support/interceptor.h
+  include/grpcpp/support/message_allocator.h
   include/grpcpp/support/proto_buffer_reader.h
   include/grpcpp/support/proto_buffer_writer.h
   include/grpcpp/support/server_callback.h
@@ -3145,6 +3247,7 @@
   include/grpcpp/impl/codegen/client_interceptor.h
   include/grpcpp/impl/codegen/client_unary_call.h
   include/grpcpp/impl/codegen/completion_queue.h
+  include/grpcpp/impl/codegen/completion_queue_impl.h
   include/grpcpp/impl/codegen/completion_queue_tag.h
   include/grpcpp/impl/codegen/config.h
   include/grpcpp/impl/codegen/core_codegen_interface.h
@@ -3153,6 +3256,7 @@
   include/grpcpp/impl/codegen/intercepted_channel.h
   include/grpcpp/impl/codegen/interceptor.h
   include/grpcpp/impl/codegen/interceptor_common.h
+  include/grpcpp/impl/codegen/message_allocator.h
   include/grpcpp/impl/codegen/metadata_map.h
   include/grpcpp/impl/codegen/method_handler_impl.h
   include/grpcpp/impl/codegen/rpc_method.h
@@ -3171,6 +3275,7 @@
   include/grpcpp/impl/codegen/stub_options.h
   include/grpcpp/impl/codegen/sync_stream.h
   include/grpcpp/impl/codegen/time.h
+  include/grpcpp/impl/codegen/sync.h
   include/grpc++/impl/codegen/proto_utils.h
   include/grpcpp/impl/codegen/proto_buffer_reader.h
   include/grpcpp/impl/codegen/proto_buffer_writer.h
@@ -3331,6 +3436,7 @@
   src/core/lib/channel/handshaker_registry.cc
   src/core/lib/channel/status_util.cc
   src/core/lib/compression/compression.cc
+  src/core/lib/compression/compression_args.cc
   src/core/lib/compression/compression_internal.cc
   src/core/lib/compression/message_compress.cc
   src/core/lib/compression/stream_compression.cc
@@ -3343,12 +3449,15 @@
   src/core/lib/http/parser.cc
   src/core/lib/iomgr/buffer_list.cc
   src/core/lib/iomgr/call_combiner.cc
+  src/core/lib/iomgr/cfstream_handle.cc
   src/core/lib/iomgr/combiner.cc
   src/core/lib/iomgr/endpoint.cc
+  src/core/lib/iomgr/endpoint_cfstream.cc
   src/core/lib/iomgr/endpoint_pair_posix.cc
   src/core/lib/iomgr/endpoint_pair_uv.cc
   src/core/lib/iomgr/endpoint_pair_windows.cc
   src/core/lib/iomgr/error.cc
+  src/core/lib/iomgr/error_cfstream.cc
   src/core/lib/iomgr/ev_epoll1_linux.cc
   src/core/lib/iomgr/ev_epollex_linux.cc
   src/core/lib/iomgr/ev_poll_posix.cc
@@ -3369,6 +3478,7 @@
   src/core/lib/iomgr/iomgr_custom.cc
   src/core/lib/iomgr/iomgr_internal.cc
   src/core/lib/iomgr/iomgr_posix.cc
+  src/core/lib/iomgr/iomgr_posix_cfstream.cc
   src/core/lib/iomgr/iomgr_uv.cc
   src/core/lib/iomgr/iomgr_windows.cc
   src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -3397,6 +3507,7 @@
   src/core/lib/iomgr/socket_utils_windows.cc
   src/core/lib/iomgr/socket_windows.cc
   src/core/lib/iomgr/tcp_client.cc
+  src/core/lib/iomgr/tcp_client_cfstream.cc
   src/core/lib/iomgr/tcp_client_custom.cc
   src/core/lib/iomgr/tcp_client_posix.cc
   src/core/lib/iomgr/tcp_client_windows.cc
@@ -3590,16 +3701,20 @@
   include/grpcpp/alarm.h
   include/grpcpp/alarm_impl.h
   include/grpcpp/channel.h
+  include/grpcpp/channel_impl.h
   include/grpcpp/client_context.h
   include/grpcpp/completion_queue.h
   include/grpcpp/create_channel.h
+  include/grpcpp/create_channel_impl.h
   include/grpcpp/create_channel_posix.h
   include/grpcpp/create_channel_posix_impl.h
   include/grpcpp/ext/health_check_service_server_builder_option.h
   include/grpcpp/generic/async_generic_service.h
   include/grpcpp/generic/generic_stub.h
+  include/grpcpp/generic/generic_stub_impl.h
   include/grpcpp/grpcpp.h
   include/grpcpp/health_check_service_interface.h
+  include/grpcpp/health_check_service_interface_impl.h
   include/grpcpp/impl/call.h
   include/grpcpp/impl/channel_argument_option.h
   include/grpcpp/impl/client_unary_call.h
@@ -3613,25 +3728,34 @@
   include/grpcpp/impl/server_builder_option_impl.h
   include/grpcpp/impl/server_builder_plugin.h
   include/grpcpp/impl/server_initializer.h
+  include/grpcpp/impl/server_initializer_impl.h
   include/grpcpp/impl/service_type.h
   include/grpcpp/resource_quota.h
+  include/grpcpp/resource_quota_impl.h
   include/grpcpp/security/auth_context.h
   include/grpcpp/security/auth_metadata_processor.h
+  include/grpcpp/security/auth_metadata_processor_impl.h
   include/grpcpp/security/credentials.h
+  include/grpcpp/security/credentials_impl.h
   include/grpcpp/security/server_credentials.h
+  include/grpcpp/security/server_credentials_impl.h
   include/grpcpp/server.h
   include/grpcpp/server_builder.h
+  include/grpcpp/server_builder_impl.h
   include/grpcpp/server_context.h
+  include/grpcpp/server_impl.h
   include/grpcpp/server_posix.h
   include/grpcpp/server_posix_impl.h
   include/grpcpp/support/async_stream.h
   include/grpcpp/support/async_unary_call.h
   include/grpcpp/support/byte_buffer.h
   include/grpcpp/support/channel_arguments.h
+  include/grpcpp/support/channel_arguments_impl.h
   include/grpcpp/support/client_callback.h
   include/grpcpp/support/client_interceptor.h
   include/grpcpp/support/config.h
   include/grpcpp/support/interceptor.h
+  include/grpcpp/support/message_allocator.h
   include/grpcpp/support/proto_buffer_reader.h
   include/grpcpp/support/proto_buffer_writer.h
   include/grpcpp/support/server_callback.h
@@ -3739,6 +3863,7 @@
   include/grpcpp/impl/codegen/client_interceptor.h
   include/grpcpp/impl/codegen/client_unary_call.h
   include/grpcpp/impl/codegen/completion_queue.h
+  include/grpcpp/impl/codegen/completion_queue_impl.h
   include/grpcpp/impl/codegen/completion_queue_tag.h
   include/grpcpp/impl/codegen/config.h
   include/grpcpp/impl/codegen/core_codegen_interface.h
@@ -3747,6 +3872,7 @@
   include/grpcpp/impl/codegen/intercepted_channel.h
   include/grpcpp/impl/codegen/interceptor.h
   include/grpcpp/impl/codegen/interceptor_common.h
+  include/grpcpp/impl/codegen/message_allocator.h
   include/grpcpp/impl/codegen/metadata_map.h
   include/grpcpp/impl/codegen/method_handler_impl.h
   include/grpcpp/impl/codegen/rpc_method.h
@@ -3765,6 +3891,7 @@
   include/grpcpp/impl/codegen/stub_options.h
   include/grpcpp/impl/codegen/sync_stream.h
   include/grpcpp/impl/codegen/time.h
+  include/grpcpp/impl/codegen/sync.h
   include/grpc/census.h
 )
   string(REPLACE "include/" "" _path ${_hdr})
@@ -3831,6 +3958,7 @@
 foreach(_hdr
   include/grpc++/support/error_details.h
   include/grpcpp/support/error_details.h
+  include/grpcpp/support/error_details_impl.h
 )
   string(REPLACE "include/" "" _path ${_hdr})
   get_filename_component(_path ${_path} PATH)
@@ -3964,6 +4092,7 @@
 foreach(_hdr
   include/grpc++/ext/proto_server_reflection_plugin.h
   include/grpcpp/ext/proto_server_reflection_plugin.h
+  include/grpcpp/ext/proto_server_reflection_plugin_impl.h
 )
   string(REPLACE "include/" "" _path ${_hdr})
   get_filename_component(_path ${_path} PATH)
@@ -4169,6 +4298,7 @@
   include/grpcpp/impl/codegen/client_interceptor.h
   include/grpcpp/impl/codegen/client_unary_call.h
   include/grpcpp/impl/codegen/completion_queue.h
+  include/grpcpp/impl/codegen/completion_queue_impl.h
   include/grpcpp/impl/codegen/completion_queue_tag.h
   include/grpcpp/impl/codegen/config.h
   include/grpcpp/impl/codegen/core_codegen_interface.h
@@ -4177,6 +4307,7 @@
   include/grpcpp/impl/codegen/intercepted_channel.h
   include/grpcpp/impl/codegen/interceptor.h
   include/grpcpp/impl/codegen/interceptor_common.h
+  include/grpcpp/impl/codegen/message_allocator.h
   include/grpcpp/impl/codegen/metadata_map.h
   include/grpcpp/impl/codegen/method_handler_impl.h
   include/grpcpp/impl/codegen/rpc_method.h
@@ -4217,6 +4348,7 @@
   include/grpc/impl/codegen/sync_generic.h
   include/grpc/impl/codegen/sync_posix.h
   include/grpc/impl/codegen/sync_windows.h
+  include/grpcpp/impl/codegen/sync.h
   include/grpc++/impl/codegen/proto_utils.h
   include/grpcpp/impl/codegen/proto_buffer_reader.h
   include/grpcpp/impl/codegen/proto_buffer_writer.h
@@ -4365,6 +4497,7 @@
   include/grpcpp/impl/codegen/client_interceptor.h
   include/grpcpp/impl/codegen/client_unary_call.h
   include/grpcpp/impl/codegen/completion_queue.h
+  include/grpcpp/impl/codegen/completion_queue_impl.h
   include/grpcpp/impl/codegen/completion_queue_tag.h
   include/grpcpp/impl/codegen/config.h
   include/grpcpp/impl/codegen/core_codegen_interface.h
@@ -4373,6 +4506,7 @@
   include/grpcpp/impl/codegen/intercepted_channel.h
   include/grpcpp/impl/codegen/interceptor.h
   include/grpcpp/impl/codegen/interceptor_common.h
+  include/grpcpp/impl/codegen/message_allocator.h
   include/grpcpp/impl/codegen/metadata_map.h
   include/grpcpp/impl/codegen/method_handler_impl.h
   include/grpcpp/impl/codegen/rpc_method.h
@@ -4413,6 +4547,7 @@
   include/grpc/impl/codegen/sync_generic.h
   include/grpc/impl/codegen/sync_posix.h
   include/grpc/impl/codegen/sync_windows.h
+  include/grpcpp/impl/codegen/sync.h
   include/grpc++/impl/codegen/proto_utils.h
   include/grpcpp/impl/codegen/proto_buffer_reader.h
   include/grpcpp/impl/codegen/proto_buffer_writer.h
@@ -4556,16 +4691,20 @@
   include/grpcpp/alarm.h
   include/grpcpp/alarm_impl.h
   include/grpcpp/channel.h
+  include/grpcpp/channel_impl.h
   include/grpcpp/client_context.h
   include/grpcpp/completion_queue.h
   include/grpcpp/create_channel.h
+  include/grpcpp/create_channel_impl.h
   include/grpcpp/create_channel_posix.h
   include/grpcpp/create_channel_posix_impl.h
   include/grpcpp/ext/health_check_service_server_builder_option.h
   include/grpcpp/generic/async_generic_service.h
   include/grpcpp/generic/generic_stub.h
+  include/grpcpp/generic/generic_stub_impl.h
   include/grpcpp/grpcpp.h
   include/grpcpp/health_check_service_interface.h
+  include/grpcpp/health_check_service_interface_impl.h
   include/grpcpp/impl/call.h
   include/grpcpp/impl/channel_argument_option.h
   include/grpcpp/impl/client_unary_call.h
@@ -4579,25 +4718,34 @@
   include/grpcpp/impl/server_builder_option_impl.h
   include/grpcpp/impl/server_builder_plugin.h
   include/grpcpp/impl/server_initializer.h
+  include/grpcpp/impl/server_initializer_impl.h
   include/grpcpp/impl/service_type.h
   include/grpcpp/resource_quota.h
+  include/grpcpp/resource_quota_impl.h
   include/grpcpp/security/auth_context.h
   include/grpcpp/security/auth_metadata_processor.h
+  include/grpcpp/security/auth_metadata_processor_impl.h
   include/grpcpp/security/credentials.h
+  include/grpcpp/security/credentials_impl.h
   include/grpcpp/security/server_credentials.h
+  include/grpcpp/security/server_credentials_impl.h
   include/grpcpp/server.h
   include/grpcpp/server_builder.h
+  include/grpcpp/server_builder_impl.h
   include/grpcpp/server_context.h
+  include/grpcpp/server_impl.h
   include/grpcpp/server_posix.h
   include/grpcpp/server_posix_impl.h
   include/grpcpp/support/async_stream.h
   include/grpcpp/support/async_unary_call.h
   include/grpcpp/support/byte_buffer.h
   include/grpcpp/support/channel_arguments.h
+  include/grpcpp/support/channel_arguments_impl.h
   include/grpcpp/support/client_callback.h
   include/grpcpp/support/client_interceptor.h
   include/grpcpp/support/config.h
   include/grpcpp/support/interceptor.h
+  include/grpcpp/support/message_allocator.h
   include/grpcpp/support/proto_buffer_reader.h
   include/grpcpp/support/proto_buffer_writer.h
   include/grpcpp/support/server_callback.h
@@ -4705,6 +4853,7 @@
   include/grpcpp/impl/codegen/client_interceptor.h
   include/grpcpp/impl/codegen/client_unary_call.h
   include/grpcpp/impl/codegen/completion_queue.h
+  include/grpcpp/impl/codegen/completion_queue_impl.h
   include/grpcpp/impl/codegen/completion_queue_tag.h
   include/grpcpp/impl/codegen/config.h
   include/grpcpp/impl/codegen/core_codegen_interface.h
@@ -4713,6 +4862,7 @@
   include/grpcpp/impl/codegen/intercepted_channel.h
   include/grpcpp/impl/codegen/interceptor.h
   include/grpcpp/impl/codegen/interceptor_common.h
+  include/grpcpp/impl/codegen/message_allocator.h
   include/grpcpp/impl/codegen/metadata_map.h
   include/grpcpp/impl/codegen/method_handler_impl.h
   include/grpcpp/impl/codegen/rpc_method.h
@@ -4731,6 +4881,7 @@
   include/grpcpp/impl/codegen/stub_options.h
   include/grpcpp/impl/codegen/sync_stream.h
   include/grpcpp/impl/codegen/time.h
+  include/grpcpp/impl/codegen/sync.h
 )
   string(REPLACE "include/" "" _path ${_hdr})
   get_filename_component(_path ${_path} PATH)
@@ -12641,6 +12792,7 @@
 target_link_libraries(client_crash_test_server
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc++_test_config
   grpc++_test_util
   grpc_test_util
   grpc++
@@ -13345,6 +13497,80 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(global_config_env_test
+  test/core/gprpp/global_config_env_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(global_config_env_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(global_config_env_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  gpr
+  grpc_test_util_unsecure
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
+add_executable(global_config_test
+  test/core/gprpp/global_config_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(global_config_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(global_config_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  gpr
+  grpc_test_util_unsecure
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(golden_file_test
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/compiler_test.pb.cc
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/compiler_test.grpc.pb.cc
@@ -13380,6 +13606,7 @@
 target_link_libraries(golden_file_test
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc++_test_config
   grpc++
   grpc
   gpr
@@ -13466,6 +13693,45 @@
 
 
 endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
+add_executable(grpc_core_map_test
+  test/core/gprpp/map_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(grpc_core_map_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(grpc_core_map_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_test_util
+  grpc++
+  grpc
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
+endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_CODEGEN)
 
 add_executable(grpc_cpp_plugin
@@ -14425,6 +14691,46 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(message_allocator_end2end_test
+  test/cpp/end2end/message_allocator_end2end_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(message_allocator_end2end_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(message_allocator_end2end_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc++_test_util
+  grpc_test_util
+  grpc++
+  grpc
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(metrics_client
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/metrics.pb.cc
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/metrics.grpc.pb.cc
@@ -15545,6 +15851,7 @@
 target_link_libraries(server_crash_test_client
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc++_test_config
   grpc++_test_util
   grpc_test_util
   grpc++
@@ -15692,6 +15999,85 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(service_config_end2end_test
+  test/cpp/end2end/service_config_end2end_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(service_config_end2end_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(service_config_end2end_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc++_test_util
+  grpc_test_util
+  grpc++
+  grpc
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
+add_executable(service_config_test
+  test/core/client_channel/service_config_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(service_config_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(service_config_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_test_util
+  grpc++
+  grpc
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(shutdown_test
   test/cpp/end2end/shutdown_test.cc
   third_party/googletest/googletest/src/gtest-all.cc
@@ -17567,6 +17953,41 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(h2_ssl_cred_reload_test
+  test/core/end2end/fixtures/h2_ssl_cred_reload.cc
+)
+
+
+target_include_directories(h2_ssl_cred_reload_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
+  PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
+  PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
+  PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
+  PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
+  PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+  PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
+)
+
+target_link_libraries(h2_ssl_cred_reload_test
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  end2end_tests
+  grpc_test_util
+  grpc
+  gpr
+)
+
+  # avoid dependency on libstdc++
+  if (_gRPC_CORE_NOSTDCXX_FLAGS)
+    set_target_properties(h2_ssl_cred_reload_test PROPERTIES LINKER_LANGUAGE C)
+    target_compile_options(h2_ssl_cred_reload_test PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${_gRPC_CORE_NOSTDCXX_FLAGS}>)
+  endif()
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(h2_ssl_proxy_test
   test/core/end2end/fixtures/h2_ssl_proxy.cc
 )
@@ -18163,6 +18584,7 @@
 target_link_libraries(resolver_component_test_unsecure
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
+  dns_test_util
   grpc++_test_util_unsecure
   grpc_test_util_unsecure
   grpc++_unsecure
@@ -18204,6 +18626,7 @@
 target_link_libraries(resolver_component_test
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
+  dns_test_util
   grpc++_test_util
   grpc_test_util
   grpc++
@@ -18413,6 +18836,7 @@
 target_link_libraries(cancel_ares_query_test
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
+  dns_test_util
   grpc++_test_util
   grpc_test_util
   grpc++
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1d14d5e..862a901 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -110,5 +110,16 @@
 - Exceptions to the rules can be made if there's a compelling reason for doing
   so.
 
+## Obtaining Commit Access
+We grant Commit Access to contributors based on the following criteria:
+* Sustained contribution to the gRPC project.
+* Deep understanding of the areas contributed to, and good consideration of various reliability, usability and performance tradeoffs. 
+* Contributions demonstrate that obtaining Commit Access will significantly reduce friction for the contributors or others. 
 
+In addition to submitting PRs, a Contributor with Commit Access can:
+* Review PRs and merge once other checks and criteria pass.
+* Triage bugs and PRs and assign appropriate labels and reviewers. 
+
+### Obtaining Commit Access without Code Contributions 
+The [gRPC organization](https://github.com/grpc) is comprised of multiple repositories and commit access is usually restricted to one or more of these repositories. Some repositories such as the [grpc.github.io](https://github.com/grpc/grpc.github.io/) do not have code, but the same principle of sustained, high quality contributions, with a good understanding of the fundamentals, apply. 
 
diff --git a/Makefile b/Makefile
index 53e70c9..c4a6222 100644
--- a/Makefile
+++ b/Makefile
@@ -460,8 +460,8 @@
 endif
 
 CORE_VERSION = 7.0.0
-CPP_VERSION = 1.20.1
-CSHARP_VERSION = 1.20.1
+CPP_VERSION = 1.21.0-dev
+CSHARP_VERSION = 1.21.0-dev
 
 CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
 CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
@@ -478,11 +478,11 @@
 DEFINES += $(EXTRA_DEFINES)
 LDLIBS += $(EXTRA_LDLIBS)
 
-HOST_CPPFLAGS = $(CPPFLAGS)
-HOST_CFLAGS = $(CFLAGS)
-HOST_CXXFLAGS = $(CXXFLAGS)
-HOST_LDFLAGS = $(LDFLAGS)
-HOST_LDLIBS = $(LDLIBS)
+HOST_CPPFLAGS += $(CPPFLAGS)
+HOST_CFLAGS += $(CFLAGS)
+HOST_CXXFLAGS += $(CXXFLAGS)
+HOST_LDFLAGS += $(LDFLAGS)
+HOST_LDLIBS += $(LDLIBS)
 
 # These are automatically computed variables.
 # There shouldn't be any need to change anything from now on.
@@ -1206,9 +1206,12 @@
 exception_test: $(BINDIR)/$(CONFIG)/exception_test
 filter_end2end_test: $(BINDIR)/$(CONFIG)/filter_end2end_test
 generic_end2end_test: $(BINDIR)/$(CONFIG)/generic_end2end_test
+global_config_env_test: $(BINDIR)/$(CONFIG)/global_config_env_test
+global_config_test: $(BINDIR)/$(CONFIG)/global_config_test
 golden_file_test: $(BINDIR)/$(CONFIG)/golden_file_test
 grpc_alts_credentials_options_test: $(BINDIR)/$(CONFIG)/grpc_alts_credentials_options_test
 grpc_cli: $(BINDIR)/$(CONFIG)/grpc_cli
+grpc_core_map_test: $(BINDIR)/$(CONFIG)/grpc_core_map_test
 grpc_cpp_plugin: $(BINDIR)/$(CONFIG)/grpc_cpp_plugin
 grpc_csharp_plugin: $(BINDIR)/$(CONFIG)/grpc_csharp_plugin
 grpc_linux_system_roots_test: $(BINDIR)/$(CONFIG)/grpc_linux_system_roots_test
@@ -1232,6 +1235,7 @@
 interop_test: $(BINDIR)/$(CONFIG)/interop_test
 json_run_localhost: $(BINDIR)/$(CONFIG)/json_run_localhost
 memory_test: $(BINDIR)/$(CONFIG)/memory_test
+message_allocator_end2end_test: $(BINDIR)/$(CONFIG)/message_allocator_end2end_test
 metrics_client: $(BINDIR)/$(CONFIG)/metrics_client
 mock_test: $(BINDIR)/$(CONFIG)/mock_test
 nonblocking_test: $(BINDIR)/$(CONFIG)/nonblocking_test
@@ -1261,6 +1265,8 @@
 server_early_return_test: $(BINDIR)/$(CONFIG)/server_early_return_test
 server_interceptors_end2end_test: $(BINDIR)/$(CONFIG)/server_interceptors_end2end_test
 server_request_call_test: $(BINDIR)/$(CONFIG)/server_request_call_test
+service_config_end2end_test: $(BINDIR)/$(CONFIG)/service_config_end2end_test
+service_config_test: $(BINDIR)/$(CONFIG)/service_config_test
 shutdown_test: $(BINDIR)/$(CONFIG)/shutdown_test
 slice_hash_table_test: $(BINDIR)/$(CONFIG)/slice_hash_table_test
 slice_weak_hash_table_test: $(BINDIR)/$(CONFIG)/slice_weak_hash_table_test
@@ -1314,6 +1320,7 @@
 h2_sockpair_1byte_test: $(BINDIR)/$(CONFIG)/h2_sockpair_1byte_test
 h2_spiffe_test: $(BINDIR)/$(CONFIG)/h2_spiffe_test
 h2_ssl_test: $(BINDIR)/$(CONFIG)/h2_ssl_test
+h2_ssl_cred_reload_test: $(BINDIR)/$(CONFIG)/h2_ssl_cred_reload_test
 h2_ssl_proxy_test: $(BINDIR)/$(CONFIG)/h2_ssl_proxy_test
 h2_uds_test: $(BINDIR)/$(CONFIG)/h2_uds_test
 inproc_test: $(BINDIR)/$(CONFIG)/inproc_test
@@ -1405,9 +1412,9 @@
 pc_cxx_unsecure: $(LIBDIR)/$(CONFIG)/pkgconfig/grpc++_unsecure.pc
 
 ifeq ($(EMBED_OPENSSL),true)
-privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libboringssl_test_util.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
+privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libboringssl_test_util.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
 else
-privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
+privatelibs_cxx:  $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_core_stats.a $(LIBDIR)/$(CONFIG)/libgrpc++_proto_reflection_desc_db.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_cli_libs.a $(LIBDIR)/$(CONFIG)/libhttp2_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_client_helper.a $(LIBDIR)/$(CONFIG)/libinterop_client_main.a $(LIBDIR)/$(CONFIG)/libinterop_server_helper.a $(LIBDIR)/$(CONFIG)/libinterop_server_lib.a $(LIBDIR)/$(CONFIG)/libinterop_server_main.a $(LIBDIR)/$(CONFIG)/libqps.a $(LIBDIR)/$(CONFIG)/libbenchmark.a
 endif
 
 
@@ -1577,6 +1584,7 @@
   $(BINDIR)/$(CONFIG)/h2_sockpair_1byte_test \
   $(BINDIR)/$(CONFIG)/h2_spiffe_test \
   $(BINDIR)/$(CONFIG)/h2_ssl_test \
+  $(BINDIR)/$(CONFIG)/h2_ssl_cred_reload_test \
   $(BINDIR)/$(CONFIG)/h2_ssl_proxy_test \
   $(BINDIR)/$(CONFIG)/h2_uds_test \
   $(BINDIR)/$(CONFIG)/inproc_test \
@@ -1677,9 +1685,12 @@
   $(BINDIR)/$(CONFIG)/exception_test \
   $(BINDIR)/$(CONFIG)/filter_end2end_test \
   $(BINDIR)/$(CONFIG)/generic_end2end_test \
+  $(BINDIR)/$(CONFIG)/global_config_env_test \
+  $(BINDIR)/$(CONFIG)/global_config_test \
   $(BINDIR)/$(CONFIG)/golden_file_test \
   $(BINDIR)/$(CONFIG)/grpc_alts_credentials_options_test \
   $(BINDIR)/$(CONFIG)/grpc_cli \
+  $(BINDIR)/$(CONFIG)/grpc_core_map_test \
   $(BINDIR)/$(CONFIG)/grpc_linux_system_roots_test \
   $(BINDIR)/$(CONFIG)/grpc_tool_test \
   $(BINDIR)/$(CONFIG)/grpclb_api_test \
@@ -1696,6 +1707,7 @@
   $(BINDIR)/$(CONFIG)/interop_test \
   $(BINDIR)/$(CONFIG)/json_run_localhost \
   $(BINDIR)/$(CONFIG)/memory_test \
+  $(BINDIR)/$(CONFIG)/message_allocator_end2end_test \
   $(BINDIR)/$(CONFIG)/metrics_client \
   $(BINDIR)/$(CONFIG)/mock_test \
   $(BINDIR)/$(CONFIG)/nonblocking_test \
@@ -1725,6 +1737,8 @@
   $(BINDIR)/$(CONFIG)/server_early_return_test \
   $(BINDIR)/$(CONFIG)/server_interceptors_end2end_test \
   $(BINDIR)/$(CONFIG)/server_request_call_test \
+  $(BINDIR)/$(CONFIG)/service_config_end2end_test \
+  $(BINDIR)/$(CONFIG)/service_config_test \
   $(BINDIR)/$(CONFIG)/shutdown_test \
   $(BINDIR)/$(CONFIG)/slice_hash_table_test \
   $(BINDIR)/$(CONFIG)/slice_weak_hash_table_test \
@@ -1818,9 +1832,12 @@
   $(BINDIR)/$(CONFIG)/exception_test \
   $(BINDIR)/$(CONFIG)/filter_end2end_test \
   $(BINDIR)/$(CONFIG)/generic_end2end_test \
+  $(BINDIR)/$(CONFIG)/global_config_env_test \
+  $(BINDIR)/$(CONFIG)/global_config_test \
   $(BINDIR)/$(CONFIG)/golden_file_test \
   $(BINDIR)/$(CONFIG)/grpc_alts_credentials_options_test \
   $(BINDIR)/$(CONFIG)/grpc_cli \
+  $(BINDIR)/$(CONFIG)/grpc_core_map_test \
   $(BINDIR)/$(CONFIG)/grpc_linux_system_roots_test \
   $(BINDIR)/$(CONFIG)/grpc_tool_test \
   $(BINDIR)/$(CONFIG)/grpclb_api_test \
@@ -1837,6 +1854,7 @@
   $(BINDIR)/$(CONFIG)/interop_test \
   $(BINDIR)/$(CONFIG)/json_run_localhost \
   $(BINDIR)/$(CONFIG)/memory_test \
+  $(BINDIR)/$(CONFIG)/message_allocator_end2end_test \
   $(BINDIR)/$(CONFIG)/metrics_client \
   $(BINDIR)/$(CONFIG)/mock_test \
   $(BINDIR)/$(CONFIG)/nonblocking_test \
@@ -1866,6 +1884,8 @@
   $(BINDIR)/$(CONFIG)/server_early_return_test \
   $(BINDIR)/$(CONFIG)/server_interceptors_end2end_test \
   $(BINDIR)/$(CONFIG)/server_request_call_test \
+  $(BINDIR)/$(CONFIG)/service_config_end2end_test \
+  $(BINDIR)/$(CONFIG)/service_config_test \
   $(BINDIR)/$(CONFIG)/shutdown_test \
   $(BINDIR)/$(CONFIG)/slice_hash_table_test \
   $(BINDIR)/$(CONFIG)/slice_weak_hash_table_test \
@@ -2307,10 +2327,16 @@
 	$(Q) $(BINDIR)/$(CONFIG)/filter_end2end_test || ( echo test filter_end2end_test failed ; exit 1 )
 	$(E) "[RUN]     Testing generic_end2end_test"
 	$(Q) $(BINDIR)/$(CONFIG)/generic_end2end_test || ( echo test generic_end2end_test failed ; exit 1 )
+	$(E) "[RUN]     Testing global_config_env_test"
+	$(Q) $(BINDIR)/$(CONFIG)/global_config_env_test || ( echo test global_config_env_test failed ; exit 1 )
+	$(E) "[RUN]     Testing global_config_test"
+	$(Q) $(BINDIR)/$(CONFIG)/global_config_test || ( echo test global_config_test failed ; exit 1 )
 	$(E) "[RUN]     Testing golden_file_test"
 	$(Q) $(BINDIR)/$(CONFIG)/golden_file_test || ( echo test golden_file_test failed ; exit 1 )
 	$(E) "[RUN]     Testing grpc_alts_credentials_options_test"
 	$(Q) $(BINDIR)/$(CONFIG)/grpc_alts_credentials_options_test || ( echo test grpc_alts_credentials_options_test failed ; exit 1 )
+	$(E) "[RUN]     Testing grpc_core_map_test"
+	$(Q) $(BINDIR)/$(CONFIG)/grpc_core_map_test || ( echo test grpc_core_map_test failed ; exit 1 )
 	$(E) "[RUN]     Testing grpc_linux_system_roots_test"
 	$(Q) $(BINDIR)/$(CONFIG)/grpc_linux_system_roots_test || ( echo test grpc_linux_system_roots_test failed ; exit 1 )
 	$(E) "[RUN]     Testing grpc_tool_test"
@@ -2333,6 +2359,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/interop_test || ( echo test interop_test failed ; exit 1 )
 	$(E) "[RUN]     Testing memory_test"
 	$(Q) $(BINDIR)/$(CONFIG)/memory_test || ( echo test memory_test failed ; exit 1 )
+	$(E) "[RUN]     Testing message_allocator_end2end_test"
+	$(Q) $(BINDIR)/$(CONFIG)/message_allocator_end2end_test || ( echo test message_allocator_end2end_test failed ; exit 1 )
 	$(E) "[RUN]     Testing mock_test"
 	$(Q) $(BINDIR)/$(CONFIG)/mock_test || ( echo test mock_test failed ; exit 1 )
 	$(E) "[RUN]     Testing nonblocking_test"
@@ -2377,6 +2405,10 @@
 	$(Q) $(BINDIR)/$(CONFIG)/server_interceptors_end2end_test || ( echo test server_interceptors_end2end_test failed ; exit 1 )
 	$(E) "[RUN]     Testing server_request_call_test"
 	$(Q) $(BINDIR)/$(CONFIG)/server_request_call_test || ( echo test server_request_call_test failed ; exit 1 )
+	$(E) "[RUN]     Testing service_config_end2end_test"
+	$(Q) $(BINDIR)/$(CONFIG)/service_config_end2end_test || ( echo test service_config_end2end_test failed ; exit 1 )
+	$(E) "[RUN]     Testing service_config_test"
+	$(Q) $(BINDIR)/$(CONFIG)/service_config_test || ( echo test service_config_test failed ; exit 1 )
 	$(E) "[RUN]     Testing shutdown_test"
 	$(Q) $(BINDIR)/$(CONFIG)/shutdown_test || ( echo test shutdown_test failed ; exit 1 )
 	$(E) "[RUN]     Testing slice_hash_table_test"
@@ -3303,7 +3335,6 @@
 
 LIBGPR_SRC = \
     src/core/lib/gpr/alloc.cc \
-    src/core/lib/gpr/arena.cc \
     src/core/lib/gpr/atm.cc \
     src/core/lib/gpr/cpu_iphone.cc \
     src/core/lib/gpr/cpu_linux.cc \
@@ -3336,7 +3367,9 @@
     src/core/lib/gpr/tmpfile_posix.cc \
     src/core/lib/gpr/tmpfile_windows.cc \
     src/core/lib/gpr/wrap_memcpy.cc \
+    src/core/lib/gprpp/arena.cc \
     src/core/lib/gprpp/fork.cc \
+    src/core/lib/gprpp/global_config_env.cc \
     src/core/lib/gprpp/thd_posix.cc \
     src/core/lib/gprpp/thd_windows.cc \
     src/core/lib/profiling/basic_timers.cc \
@@ -3427,6 +3460,7 @@
     src/core/lib/channel/handshaker_registry.cc \
     src/core/lib/channel/status_util.cc \
     src/core/lib/compression/compression.cc \
+    src/core/lib/compression/compression_args.cc \
     src/core/lib/compression/compression_internal.cc \
     src/core/lib/compression/message_compress.cc \
     src/core/lib/compression/stream_compression.cc \
@@ -3439,12 +3473,15 @@
     src/core/lib/http/parser.cc \
     src/core/lib/iomgr/buffer_list.cc \
     src/core/lib/iomgr/call_combiner.cc \
+    src/core/lib/iomgr/cfstream_handle.cc \
     src/core/lib/iomgr/combiner.cc \
     src/core/lib/iomgr/endpoint.cc \
+    src/core/lib/iomgr/endpoint_cfstream.cc \
     src/core/lib/iomgr/endpoint_pair_posix.cc \
     src/core/lib/iomgr/endpoint_pair_uv.cc \
     src/core/lib/iomgr/endpoint_pair_windows.cc \
     src/core/lib/iomgr/error.cc \
+    src/core/lib/iomgr/error_cfstream.cc \
     src/core/lib/iomgr/ev_epoll1_linux.cc \
     src/core/lib/iomgr/ev_epollex_linux.cc \
     src/core/lib/iomgr/ev_poll_posix.cc \
@@ -3465,6 +3502,7 @@
     src/core/lib/iomgr/iomgr_custom.cc \
     src/core/lib/iomgr/iomgr_internal.cc \
     src/core/lib/iomgr/iomgr_posix.cc \
+    src/core/lib/iomgr/iomgr_posix_cfstream.cc \
     src/core/lib/iomgr/iomgr_uv.cc \
     src/core/lib/iomgr/iomgr_windows.cc \
     src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -3493,6 +3531,7 @@
     src/core/lib/iomgr/socket_utils_windows.cc \
     src/core/lib/iomgr/socket_windows.cc \
     src/core/lib/iomgr/tcp_client.cc \
+    src/core/lib/iomgr/tcp_client_cfstream.cc \
     src/core/lib/iomgr/tcp_client_custom.cc \
     src/core/lib/iomgr/tcp_client_posix.cc \
     src/core/lib/iomgr/tcp_client_windows.cc \
@@ -3726,12 +3765,16 @@
     src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc \
+    src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc \
     src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc \
     src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc \
     src/core/ext/filters/census/grpc_context.cc \
@@ -3847,6 +3890,7 @@
     src/core/lib/channel/handshaker_registry.cc \
     src/core/lib/channel/status_util.cc \
     src/core/lib/compression/compression.cc \
+    src/core/lib/compression/compression_args.cc \
     src/core/lib/compression/compression_internal.cc \
     src/core/lib/compression/message_compress.cc \
     src/core/lib/compression/stream_compression.cc \
@@ -3859,12 +3903,15 @@
     src/core/lib/http/parser.cc \
     src/core/lib/iomgr/buffer_list.cc \
     src/core/lib/iomgr/call_combiner.cc \
+    src/core/lib/iomgr/cfstream_handle.cc \
     src/core/lib/iomgr/combiner.cc \
     src/core/lib/iomgr/endpoint.cc \
+    src/core/lib/iomgr/endpoint_cfstream.cc \
     src/core/lib/iomgr/endpoint_pair_posix.cc \
     src/core/lib/iomgr/endpoint_pair_uv.cc \
     src/core/lib/iomgr/endpoint_pair_windows.cc \
     src/core/lib/iomgr/error.cc \
+    src/core/lib/iomgr/error_cfstream.cc \
     src/core/lib/iomgr/ev_epoll1_linux.cc \
     src/core/lib/iomgr/ev_epollex_linux.cc \
     src/core/lib/iomgr/ev_poll_posix.cc \
@@ -3885,6 +3932,7 @@
     src/core/lib/iomgr/iomgr_custom.cc \
     src/core/lib/iomgr/iomgr_internal.cc \
     src/core/lib/iomgr/iomgr_posix.cc \
+    src/core/lib/iomgr/iomgr_posix_cfstream.cc \
     src/core/lib/iomgr/iomgr_uv.cc \
     src/core/lib/iomgr/iomgr_windows.cc \
     src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -3913,6 +3961,7 @@
     src/core/lib/iomgr/socket_utils_windows.cc \
     src/core/lib/iomgr/socket_windows.cc \
     src/core/lib/iomgr/tcp_client.cc \
+    src/core/lib/iomgr/tcp_client_cfstream.cc \
     src/core/lib/iomgr/tcp_client_custom.cc \
     src/core/lib/iomgr/tcp_client_posix.cc \
     src/core/lib/iomgr/tcp_client_windows.cc \
@@ -4251,6 +4300,7 @@
     src/core/lib/channel/handshaker_registry.cc \
     src/core/lib/channel/status_util.cc \
     src/core/lib/compression/compression.cc \
+    src/core/lib/compression/compression_args.cc \
     src/core/lib/compression/compression_internal.cc \
     src/core/lib/compression/message_compress.cc \
     src/core/lib/compression/stream_compression.cc \
@@ -4263,12 +4313,15 @@
     src/core/lib/http/parser.cc \
     src/core/lib/iomgr/buffer_list.cc \
     src/core/lib/iomgr/call_combiner.cc \
+    src/core/lib/iomgr/cfstream_handle.cc \
     src/core/lib/iomgr/combiner.cc \
     src/core/lib/iomgr/endpoint.cc \
+    src/core/lib/iomgr/endpoint_cfstream.cc \
     src/core/lib/iomgr/endpoint_pair_posix.cc \
     src/core/lib/iomgr/endpoint_pair_uv.cc \
     src/core/lib/iomgr/endpoint_pair_windows.cc \
     src/core/lib/iomgr/error.cc \
+    src/core/lib/iomgr/error_cfstream.cc \
     src/core/lib/iomgr/ev_epoll1_linux.cc \
     src/core/lib/iomgr/ev_epollex_linux.cc \
     src/core/lib/iomgr/ev_poll_posix.cc \
@@ -4289,6 +4342,7 @@
     src/core/lib/iomgr/iomgr_custom.cc \
     src/core/lib/iomgr/iomgr_internal.cc \
     src/core/lib/iomgr/iomgr_posix.cc \
+    src/core/lib/iomgr/iomgr_posix_cfstream.cc \
     src/core/lib/iomgr/iomgr_uv.cc \
     src/core/lib/iomgr/iomgr_windows.cc \
     src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -4317,6 +4371,7 @@
     src/core/lib/iomgr/socket_utils_windows.cc \
     src/core/lib/iomgr/socket_windows.cc \
     src/core/lib/iomgr/tcp_client.cc \
+    src/core/lib/iomgr/tcp_client_cfstream.cc \
     src/core/lib/iomgr/tcp_client_custom.cc \
     src/core/lib/iomgr/tcp_client_posix.cc \
     src/core/lib/iomgr/tcp_client_windows.cc \
@@ -4562,6 +4617,7 @@
     src/core/lib/channel/handshaker_registry.cc \
     src/core/lib/channel/status_util.cc \
     src/core/lib/compression/compression.cc \
+    src/core/lib/compression/compression_args.cc \
     src/core/lib/compression/compression_internal.cc \
     src/core/lib/compression/message_compress.cc \
     src/core/lib/compression/stream_compression.cc \
@@ -4574,12 +4630,15 @@
     src/core/lib/http/parser.cc \
     src/core/lib/iomgr/buffer_list.cc \
     src/core/lib/iomgr/call_combiner.cc \
+    src/core/lib/iomgr/cfstream_handle.cc \
     src/core/lib/iomgr/combiner.cc \
     src/core/lib/iomgr/endpoint.cc \
+    src/core/lib/iomgr/endpoint_cfstream.cc \
     src/core/lib/iomgr/endpoint_pair_posix.cc \
     src/core/lib/iomgr/endpoint_pair_uv.cc \
     src/core/lib/iomgr/endpoint_pair_windows.cc \
     src/core/lib/iomgr/error.cc \
+    src/core/lib/iomgr/error_cfstream.cc \
     src/core/lib/iomgr/ev_epoll1_linux.cc \
     src/core/lib/iomgr/ev_epollex_linux.cc \
     src/core/lib/iomgr/ev_poll_posix.cc \
@@ -4600,6 +4659,7 @@
     src/core/lib/iomgr/iomgr_custom.cc \
     src/core/lib/iomgr/iomgr_internal.cc \
     src/core/lib/iomgr/iomgr_posix.cc \
+    src/core/lib/iomgr/iomgr_posix_cfstream.cc \
     src/core/lib/iomgr/iomgr_uv.cc \
     src/core/lib/iomgr/iomgr_windows.cc \
     src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -4628,6 +4688,7 @@
     src/core/lib/iomgr/socket_utils_windows.cc \
     src/core/lib/iomgr/socket_windows.cc \
     src/core/lib/iomgr/tcp_client.cc \
+    src/core/lib/iomgr/tcp_client_cfstream.cc \
     src/core/lib/iomgr/tcp_client_custom.cc \
     src/core/lib/iomgr/tcp_client_posix.cc \
     src/core/lib/iomgr/tcp_client_windows.cc \
@@ -4836,6 +4897,7 @@
     src/core/lib/channel/handshaker_registry.cc \
     src/core/lib/channel/status_util.cc \
     src/core/lib/compression/compression.cc \
+    src/core/lib/compression/compression_args.cc \
     src/core/lib/compression/compression_internal.cc \
     src/core/lib/compression/message_compress.cc \
     src/core/lib/compression/stream_compression.cc \
@@ -4848,12 +4910,15 @@
     src/core/lib/http/parser.cc \
     src/core/lib/iomgr/buffer_list.cc \
     src/core/lib/iomgr/call_combiner.cc \
+    src/core/lib/iomgr/cfstream_handle.cc \
     src/core/lib/iomgr/combiner.cc \
     src/core/lib/iomgr/endpoint.cc \
+    src/core/lib/iomgr/endpoint_cfstream.cc \
     src/core/lib/iomgr/endpoint_pair_posix.cc \
     src/core/lib/iomgr/endpoint_pair_uv.cc \
     src/core/lib/iomgr/endpoint_pair_windows.cc \
     src/core/lib/iomgr/error.cc \
+    src/core/lib/iomgr/error_cfstream.cc \
     src/core/lib/iomgr/ev_epoll1_linux.cc \
     src/core/lib/iomgr/ev_epollex_linux.cc \
     src/core/lib/iomgr/ev_poll_posix.cc \
@@ -4874,6 +4939,7 @@
     src/core/lib/iomgr/iomgr_custom.cc \
     src/core/lib/iomgr/iomgr_internal.cc \
     src/core/lib/iomgr/iomgr_posix.cc \
+    src/core/lib/iomgr/iomgr_posix_cfstream.cc \
     src/core/lib/iomgr/iomgr_uv.cc \
     src/core/lib/iomgr/iomgr_windows.cc \
     src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -4902,6 +4968,7 @@
     src/core/lib/iomgr/socket_utils_windows.cc \
     src/core/lib/iomgr/socket_windows.cc \
     src/core/lib/iomgr/tcp_client.cc \
+    src/core/lib/iomgr/tcp_client_cfstream.cc \
     src/core/lib/iomgr/tcp_client_custom.cc \
     src/core/lib/iomgr/tcp_client_posix.cc \
     src/core/lib/iomgr/tcp_client_windows.cc \
@@ -5043,12 +5110,16 @@
     src/core/ext/transport/inproc/inproc_transport.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc \
+    src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc \
     src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc \
     src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc \
     src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc \
@@ -5226,6 +5297,55 @@
 endif
 
 
+LIBDNS_TEST_UTIL_SRC = \
+    test/cpp/naming/dns_test_util.cc \
+
+PUBLIC_HEADERS_CXX += \
+
+LIBDNS_TEST_UTIL_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBDNS_TEST_UTIL_SRC))))
+
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure libraries if you don't have OpenSSL.
+
+$(LIBDIR)/$(CONFIG)/libdns_test_util.a: openssl_dep_error
+
+
+else
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build a C++ library if you don't have protobuf - a bit overreached, but still okay.
+
+$(LIBDIR)/$(CONFIG)/libdns_test_util.a: protobuf_dep_error
+
+
+else
+
+$(LIBDIR)/$(CONFIG)/libdns_test_util.a: $(ZLIB_DEP) $(OPENSSL_DEP) $(CARES_DEP) $(ADDRESS_SORTING_DEP) $(PROTOBUF_DEP) $(LIBDNS_TEST_UTIL_OBJS) 
+	$(E) "[AR]      Creating $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) rm -f $(LIBDIR)/$(CONFIG)/libdns_test_util.a
+	$(Q) $(AR) $(AROPTS) $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDNS_TEST_UTIL_OBJS) 
+ifeq ($(SYSTEM),Darwin)
+	$(Q) ranlib -no_warning_for_no_symbols $(LIBDIR)/$(CONFIG)/libdns_test_util.a
+endif
+
+
+
+
+endif
+
+endif
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(LIBDNS_TEST_UTIL_OBJS:.o=.dep)
+endif
+endif
+
+
 LIBGRPC++_SRC = \
     src/cpp/client/insecure_credentials.cc \
     src/cpp/client/secure_credentials.cc \
@@ -5323,16 +5443,20 @@
     include/grpcpp/alarm.h \
     include/grpcpp/alarm_impl.h \
     include/grpcpp/channel.h \
+    include/grpcpp/channel_impl.h \
     include/grpcpp/client_context.h \
     include/grpcpp/completion_queue.h \
     include/grpcpp/create_channel.h \
+    include/grpcpp/create_channel_impl.h \
     include/grpcpp/create_channel_posix.h \
     include/grpcpp/create_channel_posix_impl.h \
     include/grpcpp/ext/health_check_service_server_builder_option.h \
     include/grpcpp/generic/async_generic_service.h \
     include/grpcpp/generic/generic_stub.h \
+    include/grpcpp/generic/generic_stub_impl.h \
     include/grpcpp/grpcpp.h \
     include/grpcpp/health_check_service_interface.h \
+    include/grpcpp/health_check_service_interface_impl.h \
     include/grpcpp/impl/call.h \
     include/grpcpp/impl/channel_argument_option.h \
     include/grpcpp/impl/client_unary_call.h \
@@ -5346,25 +5470,34 @@
     include/grpcpp/impl/server_builder_option_impl.h \
     include/grpcpp/impl/server_builder_plugin.h \
     include/grpcpp/impl/server_initializer.h \
+    include/grpcpp/impl/server_initializer_impl.h \
     include/grpcpp/impl/service_type.h \
     include/grpcpp/resource_quota.h \
+    include/grpcpp/resource_quota_impl.h \
     include/grpcpp/security/auth_context.h \
     include/grpcpp/security/auth_metadata_processor.h \
+    include/grpcpp/security/auth_metadata_processor_impl.h \
     include/grpcpp/security/credentials.h \
+    include/grpcpp/security/credentials_impl.h \
     include/grpcpp/security/server_credentials.h \
+    include/grpcpp/security/server_credentials_impl.h \
     include/grpcpp/server.h \
     include/grpcpp/server_builder.h \
+    include/grpcpp/server_builder_impl.h \
     include/grpcpp/server_context.h \
+    include/grpcpp/server_impl.h \
     include/grpcpp/server_posix.h \
     include/grpcpp/server_posix_impl.h \
     include/grpcpp/support/async_stream.h \
     include/grpcpp/support/async_unary_call.h \
     include/grpcpp/support/byte_buffer.h \
     include/grpcpp/support/channel_arguments.h \
+    include/grpcpp/support/channel_arguments_impl.h \
     include/grpcpp/support/client_callback.h \
     include/grpcpp/support/client_interceptor.h \
     include/grpcpp/support/config.h \
     include/grpcpp/support/interceptor.h \
+    include/grpcpp/support/message_allocator.h \
     include/grpcpp/support/proto_buffer_reader.h \
     include/grpcpp/support/proto_buffer_writer.h \
     include/grpcpp/support/server_callback.h \
@@ -5472,6 +5605,7 @@
     include/grpcpp/impl/codegen/client_interceptor.h \
     include/grpcpp/impl/codegen/client_unary_call.h \
     include/grpcpp/impl/codegen/completion_queue.h \
+    include/grpcpp/impl/codegen/completion_queue_impl.h \
     include/grpcpp/impl/codegen/completion_queue_tag.h \
     include/grpcpp/impl/codegen/config.h \
     include/grpcpp/impl/codegen/core_codegen_interface.h \
@@ -5480,6 +5614,7 @@
     include/grpcpp/impl/codegen/intercepted_channel.h \
     include/grpcpp/impl/codegen/interceptor.h \
     include/grpcpp/impl/codegen/interceptor_common.h \
+    include/grpcpp/impl/codegen/message_allocator.h \
     include/grpcpp/impl/codegen/metadata_map.h \
     include/grpcpp/impl/codegen/method_handler_impl.h \
     include/grpcpp/impl/codegen/rpc_method.h \
@@ -5498,6 +5633,7 @@
     include/grpcpp/impl/codegen/stub_options.h \
     include/grpcpp/impl/codegen/sync_stream.h \
     include/grpcpp/impl/codegen/time.h \
+    include/grpcpp/impl/codegen/sync.h \
     include/grpc++/impl/codegen/proto_utils.h \
     include/grpcpp/impl/codegen/proto_buffer_reader.h \
     include/grpcpp/impl/codegen/proto_buffer_writer.h \
@@ -5702,6 +5838,7 @@
     src/core/lib/channel/handshaker_registry.cc \
     src/core/lib/channel/status_util.cc \
     src/core/lib/compression/compression.cc \
+    src/core/lib/compression/compression_args.cc \
     src/core/lib/compression/compression_internal.cc \
     src/core/lib/compression/message_compress.cc \
     src/core/lib/compression/stream_compression.cc \
@@ -5714,12 +5851,15 @@
     src/core/lib/http/parser.cc \
     src/core/lib/iomgr/buffer_list.cc \
     src/core/lib/iomgr/call_combiner.cc \
+    src/core/lib/iomgr/cfstream_handle.cc \
     src/core/lib/iomgr/combiner.cc \
     src/core/lib/iomgr/endpoint.cc \
+    src/core/lib/iomgr/endpoint_cfstream.cc \
     src/core/lib/iomgr/endpoint_pair_posix.cc \
     src/core/lib/iomgr/endpoint_pair_uv.cc \
     src/core/lib/iomgr/endpoint_pair_windows.cc \
     src/core/lib/iomgr/error.cc \
+    src/core/lib/iomgr/error_cfstream.cc \
     src/core/lib/iomgr/ev_epoll1_linux.cc \
     src/core/lib/iomgr/ev_epollex_linux.cc \
     src/core/lib/iomgr/ev_poll_posix.cc \
@@ -5740,6 +5880,7 @@
     src/core/lib/iomgr/iomgr_custom.cc \
     src/core/lib/iomgr/iomgr_internal.cc \
     src/core/lib/iomgr/iomgr_posix.cc \
+    src/core/lib/iomgr/iomgr_posix_cfstream.cc \
     src/core/lib/iomgr/iomgr_uv.cc \
     src/core/lib/iomgr/iomgr_windows.cc \
     src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -5768,6 +5909,7 @@
     src/core/lib/iomgr/socket_utils_windows.cc \
     src/core/lib/iomgr/socket_windows.cc \
     src/core/lib/iomgr/tcp_client.cc \
+    src/core/lib/iomgr/tcp_client_cfstream.cc \
     src/core/lib/iomgr/tcp_client_custom.cc \
     src/core/lib/iomgr/tcp_client_posix.cc \
     src/core/lib/iomgr/tcp_client_windows.cc \
@@ -5925,16 +6067,20 @@
     include/grpcpp/alarm.h \
     include/grpcpp/alarm_impl.h \
     include/grpcpp/channel.h \
+    include/grpcpp/channel_impl.h \
     include/grpcpp/client_context.h \
     include/grpcpp/completion_queue.h \
     include/grpcpp/create_channel.h \
+    include/grpcpp/create_channel_impl.h \
     include/grpcpp/create_channel_posix.h \
     include/grpcpp/create_channel_posix_impl.h \
     include/grpcpp/ext/health_check_service_server_builder_option.h \
     include/grpcpp/generic/async_generic_service.h \
     include/grpcpp/generic/generic_stub.h \
+    include/grpcpp/generic/generic_stub_impl.h \
     include/grpcpp/grpcpp.h \
     include/grpcpp/health_check_service_interface.h \
+    include/grpcpp/health_check_service_interface_impl.h \
     include/grpcpp/impl/call.h \
     include/grpcpp/impl/channel_argument_option.h \
     include/grpcpp/impl/client_unary_call.h \
@@ -5948,25 +6094,34 @@
     include/grpcpp/impl/server_builder_option_impl.h \
     include/grpcpp/impl/server_builder_plugin.h \
     include/grpcpp/impl/server_initializer.h \
+    include/grpcpp/impl/server_initializer_impl.h \
     include/grpcpp/impl/service_type.h \
     include/grpcpp/resource_quota.h \
+    include/grpcpp/resource_quota_impl.h \
     include/grpcpp/security/auth_context.h \
     include/grpcpp/security/auth_metadata_processor.h \
+    include/grpcpp/security/auth_metadata_processor_impl.h \
     include/grpcpp/security/credentials.h \
+    include/grpcpp/security/credentials_impl.h \
     include/grpcpp/security/server_credentials.h \
+    include/grpcpp/security/server_credentials_impl.h \
     include/grpcpp/server.h \
     include/grpcpp/server_builder.h \
+    include/grpcpp/server_builder_impl.h \
     include/grpcpp/server_context.h \
+    include/grpcpp/server_impl.h \
     include/grpcpp/server_posix.h \
     include/grpcpp/server_posix_impl.h \
     include/grpcpp/support/async_stream.h \
     include/grpcpp/support/async_unary_call.h \
     include/grpcpp/support/byte_buffer.h \
     include/grpcpp/support/channel_arguments.h \
+    include/grpcpp/support/channel_arguments_impl.h \
     include/grpcpp/support/client_callback.h \
     include/grpcpp/support/client_interceptor.h \
     include/grpcpp/support/config.h \
     include/grpcpp/support/interceptor.h \
+    include/grpcpp/support/message_allocator.h \
     include/grpcpp/support/proto_buffer_reader.h \
     include/grpcpp/support/proto_buffer_writer.h \
     include/grpcpp/support/server_callback.h \
@@ -6074,6 +6229,7 @@
     include/grpcpp/impl/codegen/client_interceptor.h \
     include/grpcpp/impl/codegen/client_unary_call.h \
     include/grpcpp/impl/codegen/completion_queue.h \
+    include/grpcpp/impl/codegen/completion_queue_impl.h \
     include/grpcpp/impl/codegen/completion_queue_tag.h \
     include/grpcpp/impl/codegen/config.h \
     include/grpcpp/impl/codegen/core_codegen_interface.h \
@@ -6082,6 +6238,7 @@
     include/grpcpp/impl/codegen/intercepted_channel.h \
     include/grpcpp/impl/codegen/interceptor.h \
     include/grpcpp/impl/codegen/interceptor_common.h \
+    include/grpcpp/impl/codegen/message_allocator.h \
     include/grpcpp/impl/codegen/metadata_map.h \
     include/grpcpp/impl/codegen/method_handler_impl.h \
     include/grpcpp/impl/codegen/rpc_method.h \
@@ -6100,6 +6257,7 @@
     include/grpcpp/impl/codegen/stub_options.h \
     include/grpcpp/impl/codegen/sync_stream.h \
     include/grpcpp/impl/codegen/time.h \
+    include/grpcpp/impl/codegen/sync.h \
     include/grpc/census.h \
 
 LIBGRPC++_CRONET_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC++_CRONET_SRC))))
@@ -6172,6 +6330,7 @@
 PUBLIC_HEADERS_CXX += \
     include/grpc++/support/error_details.h \
     include/grpcpp/support/error_details.h \
+    include/grpcpp/support/error_details_impl.h \
 
 LIBGRPC++_ERROR_DETAILS_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC++_ERROR_DETAILS_SRC))))
 
@@ -6298,6 +6457,7 @@
 PUBLIC_HEADERS_CXX += \
     include/grpc++/ext/proto_server_reflection_plugin.h \
     include/grpcpp/ext/proto_server_reflection_plugin.h \
+    include/grpcpp/ext/proto_server_reflection_plugin_impl.h \
 
 LIBGRPC++_REFLECTION_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC++_REFLECTION_SRC))))
 
@@ -6476,6 +6636,7 @@
     include/grpcpp/impl/codegen/client_interceptor.h \
     include/grpcpp/impl/codegen/client_unary_call.h \
     include/grpcpp/impl/codegen/completion_queue.h \
+    include/grpcpp/impl/codegen/completion_queue_impl.h \
     include/grpcpp/impl/codegen/completion_queue_tag.h \
     include/grpcpp/impl/codegen/config.h \
     include/grpcpp/impl/codegen/core_codegen_interface.h \
@@ -6484,6 +6645,7 @@
     include/grpcpp/impl/codegen/intercepted_channel.h \
     include/grpcpp/impl/codegen/interceptor.h \
     include/grpcpp/impl/codegen/interceptor_common.h \
+    include/grpcpp/impl/codegen/message_allocator.h \
     include/grpcpp/impl/codegen/metadata_map.h \
     include/grpcpp/impl/codegen/method_handler_impl.h \
     include/grpcpp/impl/codegen/rpc_method.h \
@@ -6524,6 +6686,7 @@
     include/grpc/impl/codegen/sync_generic.h \
     include/grpc/impl/codegen/sync_posix.h \
     include/grpc/impl/codegen/sync_windows.h \
+    include/grpcpp/impl/codegen/sync.h \
     include/grpc++/impl/codegen/proto_utils.h \
     include/grpcpp/impl/codegen/proto_buffer_reader.h \
     include/grpcpp/impl/codegen/proto_buffer_writer.h \
@@ -6643,6 +6806,7 @@
     include/grpcpp/impl/codegen/client_interceptor.h \
     include/grpcpp/impl/codegen/client_unary_call.h \
     include/grpcpp/impl/codegen/completion_queue.h \
+    include/grpcpp/impl/codegen/completion_queue_impl.h \
     include/grpcpp/impl/codegen/completion_queue_tag.h \
     include/grpcpp/impl/codegen/config.h \
     include/grpcpp/impl/codegen/core_codegen_interface.h \
@@ -6651,6 +6815,7 @@
     include/grpcpp/impl/codegen/intercepted_channel.h \
     include/grpcpp/impl/codegen/interceptor.h \
     include/grpcpp/impl/codegen/interceptor_common.h \
+    include/grpcpp/impl/codegen/message_allocator.h \
     include/grpcpp/impl/codegen/metadata_map.h \
     include/grpcpp/impl/codegen/method_handler_impl.h \
     include/grpcpp/impl/codegen/rpc_method.h \
@@ -6691,6 +6856,7 @@
     include/grpc/impl/codegen/sync_generic.h \
     include/grpc/impl/codegen/sync_posix.h \
     include/grpc/impl/codegen/sync_windows.h \
+    include/grpcpp/impl/codegen/sync.h \
     include/grpc++/impl/codegen/proto_utils.h \
     include/grpcpp/impl/codegen/proto_buffer_reader.h \
     include/grpcpp/impl/codegen/proto_buffer_writer.h \
@@ -6840,16 +7006,20 @@
     include/grpcpp/alarm.h \
     include/grpcpp/alarm_impl.h \
     include/grpcpp/channel.h \
+    include/grpcpp/channel_impl.h \
     include/grpcpp/client_context.h \
     include/grpcpp/completion_queue.h \
     include/grpcpp/create_channel.h \
+    include/grpcpp/create_channel_impl.h \
     include/grpcpp/create_channel_posix.h \
     include/grpcpp/create_channel_posix_impl.h \
     include/grpcpp/ext/health_check_service_server_builder_option.h \
     include/grpcpp/generic/async_generic_service.h \
     include/grpcpp/generic/generic_stub.h \
+    include/grpcpp/generic/generic_stub_impl.h \
     include/grpcpp/grpcpp.h \
     include/grpcpp/health_check_service_interface.h \
+    include/grpcpp/health_check_service_interface_impl.h \
     include/grpcpp/impl/call.h \
     include/grpcpp/impl/channel_argument_option.h \
     include/grpcpp/impl/client_unary_call.h \
@@ -6863,25 +7033,34 @@
     include/grpcpp/impl/server_builder_option_impl.h \
     include/grpcpp/impl/server_builder_plugin.h \
     include/grpcpp/impl/server_initializer.h \
+    include/grpcpp/impl/server_initializer_impl.h \
     include/grpcpp/impl/service_type.h \
     include/grpcpp/resource_quota.h \
+    include/grpcpp/resource_quota_impl.h \
     include/grpcpp/security/auth_context.h \
     include/grpcpp/security/auth_metadata_processor.h \
+    include/grpcpp/security/auth_metadata_processor_impl.h \
     include/grpcpp/security/credentials.h \
+    include/grpcpp/security/credentials_impl.h \
     include/grpcpp/security/server_credentials.h \
+    include/grpcpp/security/server_credentials_impl.h \
     include/grpcpp/server.h \
     include/grpcpp/server_builder.h \
+    include/grpcpp/server_builder_impl.h \
     include/grpcpp/server_context.h \
+    include/grpcpp/server_impl.h \
     include/grpcpp/server_posix.h \
     include/grpcpp/server_posix_impl.h \
     include/grpcpp/support/async_stream.h \
     include/grpcpp/support/async_unary_call.h \
     include/grpcpp/support/byte_buffer.h \
     include/grpcpp/support/channel_arguments.h \
+    include/grpcpp/support/channel_arguments_impl.h \
     include/grpcpp/support/client_callback.h \
     include/grpcpp/support/client_interceptor.h \
     include/grpcpp/support/config.h \
     include/grpcpp/support/interceptor.h \
+    include/grpcpp/support/message_allocator.h \
     include/grpcpp/support/proto_buffer_reader.h \
     include/grpcpp/support/proto_buffer_writer.h \
     include/grpcpp/support/server_callback.h \
@@ -6989,6 +7168,7 @@
     include/grpcpp/impl/codegen/client_interceptor.h \
     include/grpcpp/impl/codegen/client_unary_call.h \
     include/grpcpp/impl/codegen/completion_queue.h \
+    include/grpcpp/impl/codegen/completion_queue_impl.h \
     include/grpcpp/impl/codegen/completion_queue_tag.h \
     include/grpcpp/impl/codegen/config.h \
     include/grpcpp/impl/codegen/core_codegen_interface.h \
@@ -6997,6 +7177,7 @@
     include/grpcpp/impl/codegen/intercepted_channel.h \
     include/grpcpp/impl/codegen/interceptor.h \
     include/grpcpp/impl/codegen/interceptor_common.h \
+    include/grpcpp/impl/codegen/message_allocator.h \
     include/grpcpp/impl/codegen/metadata_map.h \
     include/grpcpp/impl/codegen/method_handler_impl.h \
     include/grpcpp/impl/codegen/rpc_method.h \
@@ -7015,6 +7196,7 @@
     include/grpcpp/impl/codegen/stub_options.h \
     include/grpcpp/impl/codegen/sync_stream.h \
     include/grpcpp/impl/codegen/time.h \
+    include/grpcpp/impl/codegen/sync.h \
 
 LIBGRPC++_UNSECURE_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC++_UNSECURE_SRC))))
 
@@ -15575,16 +15757,16 @@
 
 else
 
-$(BINDIR)/$(CONFIG)/client_crash_test_server: $(PROTOBUF_DEP) $(CLIENT_CRASH_TEST_SERVER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(BINDIR)/$(CONFIG)/client_crash_test_server: $(PROTOBUF_DEP) $(CLIENT_CRASH_TEST_SERVER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(CLIENT_CRASH_TEST_SERVER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/client_crash_test_server
+	$(Q) $(LDXX) $(LDFLAGS) $(CLIENT_CRASH_TEST_SERVER_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/client_crash_test_server
 
 endif
 
 endif
 
-$(OBJDIR)/$(CONFIG)/test/cpp/end2end/client_crash_test_server.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(OBJDIR)/$(CONFIG)/test/cpp/end2end/client_crash_test_server.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
 
 deps_client_crash_test_server: $(CLIENT_CRASH_TEST_SERVER_OBJS:.o=.dep)
 
@@ -16298,6 +16480,92 @@
 endif
 
 
+GLOBAL_CONFIG_ENV_TEST_SRC = \
+    test/core/gprpp/global_config_env_test.cc \
+
+GLOBAL_CONFIG_ENV_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GLOBAL_CONFIG_ENV_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/global_config_env_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/global_config_env_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/global_config_env_test: $(PROTOBUF_DEP) $(GLOBAL_CONFIG_ENV_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(GLOBAL_CONFIG_ENV_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/global_config_env_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/gprpp/global_config_env_test.o:  $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a
+
+deps_global_config_env_test: $(GLOBAL_CONFIG_ENV_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GLOBAL_CONFIG_ENV_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
+GLOBAL_CONFIG_TEST_SRC = \
+    test/core/gprpp/global_config_test.cc \
+
+GLOBAL_CONFIG_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GLOBAL_CONFIG_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/global_config_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/global_config_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/global_config_test: $(PROTOBUF_DEP) $(GLOBAL_CONFIG_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(GLOBAL_CONFIG_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/global_config_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/gprpp/global_config_test.o:  $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a
+
+deps_global_config_test: $(GLOBAL_CONFIG_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GLOBAL_CONFIG_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 GOLDEN_FILE_TEST_SRC = \
     $(GENDIR)/src/proto/grpc/testing/compiler_test.pb.cc $(GENDIR)/src/proto/grpc/testing/compiler_test.grpc.pb.cc \
     test/cpp/codegen/golden_file_test.cc \
@@ -16322,18 +16590,18 @@
 
 else
 
-$(BINDIR)/$(CONFIG)/golden_file_test: $(PROTOBUF_DEP) $(GOLDEN_FILE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(BINDIR)/$(CONFIG)/golden_file_test: $(PROTOBUF_DEP) $(GOLDEN_FILE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(GOLDEN_FILE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/golden_file_test
+	$(Q) $(LDXX) $(LDFLAGS) $(GOLDEN_FILE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/golden_file_test
 
 endif
 
 endif
 
-$(OBJDIR)/$(CONFIG)/src/proto/grpc/testing/compiler_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(OBJDIR)/$(CONFIG)/src/proto/grpc/testing/compiler_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
 
-$(OBJDIR)/$(CONFIG)/test/cpp/codegen/golden_file_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(OBJDIR)/$(CONFIG)/test/cpp/codegen/golden_file_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
 
 deps_golden_file_test: $(GOLDEN_FILE_TEST_OBJS:.o=.dep)
 
@@ -16431,6 +16699,49 @@
 endif
 
 
+GRPC_CORE_MAP_TEST_SRC = \
+    test/core/gprpp/map_test.cc \
+
+GRPC_CORE_MAP_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_CORE_MAP_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/grpc_core_map_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/grpc_core_map_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/grpc_core_map_test: $(PROTOBUF_DEP) $(GRPC_CORE_MAP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(GRPC_CORE_MAP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/grpc_core_map_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/gprpp/map_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_grpc_core_map_test: $(GRPC_CORE_MAP_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GRPC_CORE_MAP_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 GRPC_CPP_PLUGIN_SRC = \
     src/compiler/cpp_plugin.cc \
 
@@ -17315,6 +17626,49 @@
 endif
 
 
+MESSAGE_ALLOCATOR_END2END_TEST_SRC = \
+    test/cpp/end2end/message_allocator_end2end_test.cc \
+
+MESSAGE_ALLOCATOR_END2END_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(MESSAGE_ALLOCATOR_END2END_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/message_allocator_end2end_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/message_allocator_end2end_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/message_allocator_end2end_test: $(PROTOBUF_DEP) $(MESSAGE_ALLOCATOR_END2END_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(MESSAGE_ALLOCATOR_END2END_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/message_allocator_end2end_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/cpp/end2end/message_allocator_end2end_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_message_allocator_end2end_test: $(MESSAGE_ALLOCATOR_END2END_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(MESSAGE_ALLOCATOR_END2END_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 METRICS_CLIENT_SRC = \
     $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc \
     test/cpp/interop/metrics_client.cc \
@@ -18452,16 +18806,16 @@
 
 else
 
-$(BINDIR)/$(CONFIG)/server_crash_test_client: $(PROTOBUF_DEP) $(SERVER_CRASH_TEST_CLIENT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(BINDIR)/$(CONFIG)/server_crash_test_client: $(PROTOBUF_DEP) $(SERVER_CRASH_TEST_CLIENT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(SERVER_CRASH_TEST_CLIENT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/server_crash_test_client
+	$(Q) $(LDXX) $(LDFLAGS) $(SERVER_CRASH_TEST_CLIENT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/server_crash_test_client
 
 endif
 
 endif
 
-$(OBJDIR)/$(CONFIG)/test/cpp/end2end/server_crash_test_client.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+$(OBJDIR)/$(CONFIG)/test/cpp/end2end/server_crash_test_client.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
 
 deps_server_crash_test_client: $(SERVER_CRASH_TEST_CLIENT_OBJS:.o=.dep)
 
@@ -18611,6 +18965,92 @@
 $(OBJDIR)/$(CONFIG)/test/cpp/server/server_request_call_test.o: $(GENDIR)/src/proto/grpc/testing/echo_messages.pb.cc $(GENDIR)/src/proto/grpc/testing/echo_messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.grpc.pb.cc
 
 
+SERVICE_CONFIG_END2END_TEST_SRC = \
+    test/cpp/end2end/service_config_end2end_test.cc \
+
+SERVICE_CONFIG_END2END_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(SERVICE_CONFIG_END2END_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/service_config_end2end_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/service_config_end2end_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/service_config_end2end_test: $(PROTOBUF_DEP) $(SERVICE_CONFIG_END2END_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(SERVICE_CONFIG_END2END_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/service_config_end2end_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/cpp/end2end/service_config_end2end_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_service_config_end2end_test: $(SERVICE_CONFIG_END2END_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(SERVICE_CONFIG_END2END_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
+SERVICE_CONFIG_TEST_SRC = \
+    test/core/client_channel/service_config_test.cc \
+
+SERVICE_CONFIG_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(SERVICE_CONFIG_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/service_config_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
+
+$(BINDIR)/$(CONFIG)/service_config_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/service_config_test: $(PROTOBUF_DEP) $(SERVICE_CONFIG_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(SERVICE_CONFIG_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/service_config_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/client_channel/service_config_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_service_config_test: $(SERVICE_CONFIG_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(SERVICE_CONFIG_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 SHUTDOWN_TEST_SRC = \
     test/cpp/end2end/shutdown_test.cc \
 
@@ -20539,6 +20979,38 @@
 endif
 
 
+H2_SSL_CRED_RELOAD_TEST_SRC = \
+    test/core/end2end/fixtures/h2_ssl_cred_reload.cc \
+
+H2_SSL_CRED_RELOAD_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(H2_SSL_CRED_RELOAD_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/h2_ssl_cred_reload_test: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/h2_ssl_cred_reload_test: $(H2_SSL_CRED_RELOAD_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libend2end_tests.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(H2_SSL_CRED_RELOAD_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libend2end_tests.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/h2_ssl_cred_reload_test
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/end2end/fixtures/h2_ssl_cred_reload.o:  $(LIBDIR)/$(CONFIG)/libend2end_tests.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_h2_ssl_cred_reload_test: $(H2_SSL_CRED_RELOAD_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(H2_SSL_CRED_RELOAD_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 H2_SSL_PROXY_TEST_SRC = \
     test/core/end2end/fixtures/h2_ssl_proxy.cc \
 
@@ -20918,16 +21390,16 @@
 
 else
 
-$(BINDIR)/$(CONFIG)/resolver_component_test_unsecure: $(PROTOBUF_DEP) $(RESOLVER_COMPONENT_TEST_UNSECURE_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
+$(BINDIR)/$(CONFIG)/resolver_component_test_unsecure: $(PROTOBUF_DEP) $(RESOLVER_COMPONENT_TEST_UNSECURE_OBJS) $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(RESOLVER_COMPONENT_TEST_UNSECURE_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/resolver_component_test_unsecure
+	$(Q) $(LDXX) $(LDFLAGS) $(RESOLVER_COMPONENT_TEST_UNSECURE_OBJS) $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/resolver_component_test_unsecure
 
 endif
 
 endif
 
-$(OBJDIR)/$(CONFIG)/test/cpp/naming/resolver_component_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
+$(OBJDIR)/$(CONFIG)/test/cpp/naming/resolver_component_test.o:  $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc++_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 
 deps_resolver_component_test_unsecure: $(RESOLVER_COMPONENT_TEST_UNSECURE_OBJS:.o=.dep)
 
@@ -20961,16 +21433,16 @@
 
 else
 
-$(BINDIR)/$(CONFIG)/resolver_component_test: $(PROTOBUF_DEP) $(RESOLVER_COMPONENT_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
+$(BINDIR)/$(CONFIG)/resolver_component_test: $(PROTOBUF_DEP) $(RESOLVER_COMPONENT_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(RESOLVER_COMPONENT_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/resolver_component_test
+	$(Q) $(LDXX) $(LDFLAGS) $(RESOLVER_COMPONENT_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/resolver_component_test
 
 endif
 
 endif
 
-$(OBJDIR)/$(CONFIG)/test/cpp/naming/resolver_component_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
+$(OBJDIR)/$(CONFIG)/test/cpp/naming/resolver_component_test.o:  $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 
 deps_resolver_component_test: $(RESOLVER_COMPONENT_TEST_OBJS:.o=.dep)
 
@@ -21176,16 +21648,16 @@
 
 else
 
-$(BINDIR)/$(CONFIG)/cancel_ares_query_test: $(PROTOBUF_DEP) $(CANCEL_ARES_QUERY_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
+$(BINDIR)/$(CONFIG)/cancel_ares_query_test: $(PROTOBUF_DEP) $(CANCEL_ARES_QUERY_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 	$(E) "[LD]      Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(LDXX) $(LDFLAGS) $(CANCEL_ARES_QUERY_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/cancel_ares_query_test
+	$(Q) $(LDXX) $(LDFLAGS) $(CANCEL_ARES_QUERY_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/cancel_ares_query_test
 
 endif
 
 endif
 
-$(OBJDIR)/$(CONFIG)/test/cpp/naming/cancel_ares_query_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
+$(OBJDIR)/$(CONFIG)/test/cpp/naming/cancel_ares_query_test.o:  $(LIBDIR)/$(CONFIG)/libdns_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
 
 deps_cancel_ares_query_test: $(CANCEL_ARES_QUERY_TEST_OBJS:.o=.dep)
 
@@ -21815,6 +22287,7 @@
 test/cpp/interop/interop_server_bootstrap.cc: $(OPENSSL_DEP)
 test/cpp/interop/server_helper.cc: $(OPENSSL_DEP)
 test/cpp/microbenchmarks/helpers.cc: $(OPENSSL_DEP)
+test/cpp/naming/dns_test_util.cc: $(OPENSSL_DEP)
 test/cpp/qps/benchmark_config.cc: $(OPENSSL_DEP)
 test/cpp/qps/client_async.cc: $(OPENSSL_DEP)
 test/cpp/qps/client_callback.cc: $(OPENSSL_DEP)
diff --git a/WORKSPACE b/WORKSPACE
index 81371bf..2db3c5d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -9,12 +9,26 @@
 grpc_test_only_deps()
 
 register_execution_platforms(
-    "//third_party/toolchains:rbe_ubuntu1604",
-    "//third_party/toolchains:rbe_ubuntu1604_large",
+    "//third_party/toolchains:local",
+    "//third_party/toolchains:local_large",
+    "//third_party/toolchains:rbe_windows",
 )
 
 register_toolchains(
-    "//third_party/toolchains:cc-toolchain-clang-x86_64-default",
+    "//third_party/toolchains/bazel_0.23.2_rbe_windows:cc-toolchain-x64_windows",
+)
+
+git_repository(
+    name = "io_bazel_rules_python",
+    commit = "8b5d0683a7d878b28fffe464779c8a53659fc645",
+    remote = "https://github.com/bazelbuild/rules_python.git",
+)
+
+load("@io_bazel_rules_python//python:pip.bzl", "pip_repositories", "pip_import")
+
+pip_import(
+    name = "grpc_python_dependencies",
+    requirements = "//:requirements.bazel.txt",
 )
 
 http_archive(
@@ -27,36 +41,27 @@
     ],
 )
 
-load("//third_party/py:python_configure.bzl", "python_configure")
+load("//bazel:grpc_python_deps.bzl", "grpc_python_deps")
 
-python_configure(name = "local_config_python")
+grpc_python_deps()
 
-git_repository(
-    name = "io_bazel_rules_python",
-    commit = "8b5d0683a7d878b28fffe464779c8a53659fc645",
-    remote = "https://github.com/bazelbuild/rules_python.git",
+load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig")
+
+# Create toolchain configuration for remote execution.
+rbe_autoconfig(
+    name = "rbe_default",
 )
 
-load("@io_bazel_rules_python//python:pip.bzl", "pip_repositories", "pip_import")
+load("@bazel_toolchains//rules:environments.bzl", "clang_env")
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
 
-pip_repositories()
-
-pip_import(
-    name = "grpc_python_dependencies",
-    requirements = "//:requirements.bazel.txt",
+# Create msan toolchain configuration for remote execution.
+rbe_autoconfig(
+    name = "rbe_msan",
+    env = dicts.add(
+        clang_env(),
+        {
+            "BAZEL_LINKOPTS": "-lc++:-lc++abi:-lm",
+        },
+    ),
 )
-
-load("@grpc_python_dependencies//:requirements.bzl", "pip_install")
-
-pip_install()
-
-# NOTE(https://github.com/pubref/rules_protobuf/pull/196): Switch to upstream repo after this gets merged.
-git_repository(
-    name = "org_pubref_rules_protobuf",
-    remote = "https://github.com/ghostwriternr/rules_protobuf",
-    tag = "v0.8.2.1-alpha",
-)
-
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_repositories")
-
-py_proto_repositories()
diff --git a/bazel/BUILD b/bazel/BUILD
index 3240289..c3c82c9 100644
--- a/bazel/BUILD
+++ b/bazel/BUILD
@@ -17,15 +17,3 @@
 package(default_visibility = ["//:__subpackages__"])
 
 load(":cc_grpc_library.bzl", "cc_grpc_library")
-
-proto_library(
-    name = "well_known_protos_list",
-    srcs = ["@com_google_protobuf//:well_known_protos"],
-)
-
-cc_grpc_library(
-    name = "well_known_protos",
-    srcs = "well_known_protos_list",
-    proto_only = True,
-    deps = [],
-)
diff --git a/bazel/cc_grpc_library.bzl b/bazel/cc_grpc_library.bzl
index 6bfcd65..572af75 100644
--- a/bazel/cc_grpc_library.bzl
+++ b/bazel/cc_grpc_library.bzl
@@ -1,71 +1,105 @@
 """Generates and compiles C++ grpc stubs from proto_library rules."""
 
 load("//bazel:generate_cc.bzl", "generate_cc")
+load("//bazel:protobuf.bzl", "well_known_proto_libs")
 
-def cc_grpc_library(name, srcs, deps, proto_only, well_known_protos, generate_mocks = False, use_external = False, **kwargs):
-  """Generates C++ grpc classes from a .proto file.
+def cc_grpc_library(
+        name,
+        srcs,
+        deps,
+        proto_only = False,
+        well_known_protos = False,
+        generate_mocks = False,
+        use_external = False,
+        grpc_only = False,
+        **kwargs):
+    """Generates C++ grpc classes for services defined in a proto file.
 
-  Assumes the generated classes will be used in cc_api_version = 2.
+    If grpc_only is True, this rule is compatible with proto_library and
+    cc_proto_library native rules such that it expects proto_library target
+    as srcs argument and generates only grpc library classes, expecting
+    protobuf messages classes library (cc_proto_library target) to be passed in
+    deps argument. By default grpc_only is False which makes this rule to behave
+    in a backwards-compatible mode (trying to generate both proto and grpc
+    classes).
 
-  Arguments:
-      name: name of rule.
-      srcs: a single proto_library, which wraps the .proto files with services.
-      deps: a list of C++ proto_library (or cc_proto_library) which provides
-        the compiled code of any message that the services depend on.
-      well_known_protos: Should this library additionally depend on well known
-        protos
-      use_external: When True the grpc deps are prefixed with //external. This
-        allows grpc to be used as a dependency in other bazel projects.
-      generate_mocks: When True, Google Mock code for client stub is generated.
-      **kwargs: rest of arguments, e.g., compatible_with and visibility.
-  """
-  if len(srcs) > 1:
-    fail("Only one srcs value supported", "srcs")
+    Assumes the generated classes will be used in cc_api_version = 2.
 
-  proto_target = "_" + name + "_only"
-  codegen_target = "_" + name + "_codegen"
-  codegen_grpc_target = "_" + name + "_grpc_codegen"
-  proto_deps = ["_" + dep + "_only" for dep in deps if dep.find(':') == -1]
-  proto_deps += [dep.split(':')[0] + ':' + "_" + dep.split(':')[1] + "_only" for dep in deps if dep.find(':') != -1]
+    Args:
+        name (str): Name of rule.
+        srcs (list): A single .proto file which contains services definitions,
+          or if grpc_only parameter is True, a single proto_library which
+          contains services descriptors.
+        deps (list): A list of C++ proto_library (or cc_proto_library) which
+          provides the compiled code of any message that the services depend on.
+        proto_only (bool): If True, create only C++ proto classes library,
+          avoid creating C++ grpc classes library (expect it in deps).
+          Deprecated, use native cc_proto_library instead. False by default.
+        well_known_protos (bool): Should this library additionally depend on
+          well known protos. Deprecated, the well known protos should be
+          specified as explicit dependencies of the proto_library target
+          (passed in srcs parameter) instead. False by default.
+        generate_mocks (bool): when True, Google Mock code for client stub is
+          generated. False by default.
+        use_external (bool): Not used.
+        grpc_only (bool): if True, generate only grpc library, expecting
+          protobuf messages library (cc_proto_library target) to be passed as
+          deps. False by default (will become True by default eventually).
+        **kwargs: rest of arguments, e.g., compatible_with and visibility
+    """
+    if len(srcs) > 1:
+        fail("Only one srcs value supported", "srcs")
+    if grpc_only and proto_only:
+        fail("A mutualy exclusive configuration is specified: grpc_only = True and proto_only = True")
 
-  native.proto_library(
-      name = proto_target,
-      srcs = srcs,
-      deps = proto_deps,
-      **kwargs
-  )
+    extra_deps = []
+    proto_targets = []
 
-  generate_cc(
-      name = codegen_target,
-      srcs = [proto_target],
-      well_known_protos = well_known_protos,
-      **kwargs
-  )
+    if not grpc_only:
+        proto_target = "_" + name + "_only"
+        cc_proto_target = name if proto_only else "_" + name + "_cc_proto"
 
-  if not proto_only:
-    plugin = "@com_github_grpc_grpc//:grpc_cpp_plugin"
-    generate_cc(
-        name = codegen_grpc_target,
-        srcs = [proto_target],
-        plugin = plugin,
-        well_known_protos = well_known_protos,
-        generate_mocks = generate_mocks,
-        **kwargs
-    )
-    grpc_deps  = ["@com_github_grpc_grpc//:grpc++_codegen_proto",
-                  "//external:protobuf"]
-    native.cc_library(
-        name = name,
-        srcs = [":" + codegen_grpc_target, ":" + codegen_target],
-        hdrs = [":" + codegen_grpc_target, ":" + codegen_target],
-        deps = deps + grpc_deps,
-        **kwargs
-    )
-  else:
-    native.cc_library(
-        name = name,
-        srcs = [":" + codegen_target],
-        hdrs = [":" + codegen_target],
-        deps = deps + ["//external:protobuf"],
-        **kwargs
-    )
+        proto_deps = ["_" + dep + "_only" for dep in deps if dep.find(":") == -1]
+        proto_deps += [dep.split(":")[0] + ":" + "_" + dep.split(":")[1] + "_only" for dep in deps if dep.find(":") != -1]
+        if well_known_protos:
+            proto_deps += well_known_proto_libs()
+
+        native.proto_library(
+            name = proto_target,
+            srcs = srcs,
+            deps = proto_deps,
+            **kwargs
+        )
+
+        native.cc_proto_library(
+            name = cc_proto_target,
+            deps = [":" + proto_target],
+            **kwargs
+        )
+        extra_deps.append(":" + cc_proto_target)
+        proto_targets.append(proto_target)
+    else:
+        if not srcs:
+            fail("srcs cannot be empty", "srcs")
+        proto_targets += srcs
+
+    if not proto_only:
+        codegen_grpc_target = "_" + name + "_grpc_codegen"
+        generate_cc(
+            name = codegen_grpc_target,
+            srcs = proto_targets,
+            plugin = "@com_github_grpc_grpc//:grpc_cpp_plugin",
+            well_known_protos = well_known_protos,
+            generate_mocks = generate_mocks,
+            **kwargs
+        )
+
+        native.cc_library(
+            name = name,
+            srcs = [":" + codegen_grpc_target],
+            hdrs = [":" + codegen_grpc_target],
+            deps = deps +
+                   extra_deps +
+                   ["@com_github_grpc_grpc//:grpc++_codegen_proto"],
+            **kwargs
+        )
diff --git a/bazel/generate_cc.bzl b/bazel/generate_cc.bzl
index 8f30c84..29a888f 100644
--- a/bazel/generate_cc.bzl
+++ b/bazel/generate_cc.bzl
@@ -4,81 +4,142 @@
 directly.
 """
 
+load(
+    "//bazel:protobuf.bzl",
+    "get_include_protoc_args",
+    "get_plugin_args",
+    "get_proto_root",
+    "proto_path_to_generated_filename",
+)
+
+_GRPC_PROTO_HEADER_FMT = "{}.grpc.pb.h"
+_GRPC_PROTO_SRC_FMT = "{}.grpc.pb.cc"
+_GRPC_PROTO_MOCK_HEADER_FMT = "{}_mock.grpc.pb.h"
+_PROTO_HEADER_FMT = "{}.pb.h"
+_PROTO_SRC_FMT = "{}.pb.cc"
+
+def _strip_package_from_path(label_package, file):
+    prefix_len = 0
+    if not file.is_source and file.path.startswith(file.root.path):
+        prefix_len = len(file.root.path) + 1
+
+    path = file.path
+    if len(label_package) == 0:
+        return path
+    if not path.startswith(label_package + "/", prefix_len):
+        fail("'{}' does not lie within '{}'.".format(path, label_package))
+    return path[prefix_len + len(label_package + "/"):]
+
+def _get_srcs_file_path(file):
+    if not file.is_source and file.path.startswith(file.root.path):
+        return file.path[len(file.root.path) + 1:]
+    return file.path
+
+def _join_directories(directories):
+    massaged_directories = [directory for directory in directories if len(directory) != 0]
+    return "/".join(massaged_directories)
+
 def generate_cc_impl(ctx):
-  """Implementation of the generate_cc rule."""
-  protos = [f for src in ctx.attr.srcs for f in src.proto.direct_sources]
-  includes = [f for src in ctx.attr.srcs for f in src.proto.transitive_imports]
-  outs = []
-  # label_len is length of the path from WORKSPACE root to the location of this build file
-  label_len = 0
-  # proto_root is the directory relative to which generated include paths should be
-  proto_root = ""
-  if ctx.label.package:
-    # The +1 is for the trailing slash.
-    label_len += len(ctx.label.package) + 1
-  if ctx.label.workspace_root:
-    label_len += len(ctx.label.workspace_root) + 1
-    proto_root = "/" + ctx.label.workspace_root
+    """Implementation of the generate_cc rule."""
+    protos = [f for src in ctx.attr.srcs for f in src.proto.check_deps_sources]
+    includes = [
+        f
+        for src in ctx.attr.srcs
+        for f in src.proto.transitive_imports
+    ]
+    outs = []
+    proto_root = get_proto_root(
+        ctx.label.workspace_root,
+    )
 
-  if ctx.executable.plugin:
-    outs += [proto.path[label_len:-len(".proto")] + ".grpc.pb.h" for proto in protos]
-    outs += [proto.path[label_len:-len(".proto")] + ".grpc.pb.cc" for proto in protos]
-    if ctx.attr.generate_mocks:
-      outs += [proto.path[label_len:-len(".proto")] + "_mock.grpc.pb.h" for proto in protos]
-  else:
-    outs += [proto.path[label_len:-len(".proto")] + ".pb.h" for proto in protos]
-    outs += [proto.path[label_len:-len(".proto")] + ".pb.cc" for proto in protos]
-  out_files = [ctx.actions.declare_file(out) for out in outs]
-  dir_out = str(ctx.genfiles_dir.path + proto_root)
-
-  arguments = []
-  if ctx.executable.plugin:
-    arguments += ["--plugin=protoc-gen-PLUGIN=" + ctx.executable.plugin.path]
-    flags = list(ctx.attr.flags)
-    if ctx.attr.generate_mocks:
-      flags.append("generate_mock_code=true")
-    arguments += ["--PLUGIN_out=" + ",".join(flags) + ":" + dir_out]
-    tools = [ctx.executable.plugin]
-  else:
-    arguments += ["--cpp_out=" + ",".join(ctx.attr.flags) + ":" + dir_out]
-    tools = []
-
-  # Import protos relative to their workspace root so that protoc prints the
-  # right include paths.
-  for include in includes:
-    directory = include.path
-    if directory.startswith("external"):
-      external_sep = directory.find("/")
-      repository_sep = directory.find("/", external_sep + 1)
-      arguments += ["--proto_path=" + directory[:repository_sep]]
+    label_package = _join_directories([ctx.label.workspace_root, ctx.label.package])
+    if ctx.executable.plugin:
+        outs += [
+            proto_path_to_generated_filename(
+                _strip_package_from_path(label_package, proto),
+                _GRPC_PROTO_HEADER_FMT,
+            )
+            for proto in protos
+        ]
+        outs += [
+            proto_path_to_generated_filename(
+                _strip_package_from_path(label_package, proto),
+                _GRPC_PROTO_SRC_FMT,
+            )
+            for proto in protos
+        ]
+        if ctx.attr.generate_mocks:
+            outs += [
+                proto_path_to_generated_filename(
+                    _strip_package_from_path(label_package, proto),
+                    _GRPC_PROTO_MOCK_HEADER_FMT,
+                )
+                for proto in protos
+            ]
     else:
-      arguments += ["--proto_path=."]
-  # Include the output directory so that protoc puts the generated code in the
-  # right directory.
-  arguments += ["--proto_path={0}{1}".format(dir_out, proto_root)]
-  arguments += [proto.path for proto in protos]
+        outs += [
+            proto_path_to_generated_filename(
+                _strip_package_from_path(label_package, proto),
+                _PROTO_HEADER_FMT,
+            )
+            for proto in protos
+        ]
+        outs += [
+            proto_path_to_generated_filename(
+                _strip_package_from_path(label_package, proto),
+                _PROTO_SRC_FMT,
+            )
+            for proto in protos
+        ]
+    out_files = [ctx.actions.declare_file(out) for out in outs]
+    dir_out = str(ctx.genfiles_dir.path + proto_root)
 
-  # create a list of well known proto files if the argument is non-None
-  well_known_proto_files = []
-  if ctx.attr.well_known_protos:
-    f = ctx.attr.well_known_protos.files.to_list()[0].dirname
-    if f != "external/com_google_protobuf/src/google/protobuf":
-      print("Error: Only @com_google_protobuf//:well_known_protos is supported")
+    arguments = []
+    if ctx.executable.plugin:
+        arguments += get_plugin_args(
+            ctx.executable.plugin,
+            ctx.attr.flags,
+            dir_out,
+            ctx.attr.generate_mocks,
+        )
+        tools = [ctx.executable.plugin]
     else:
-      # f points to "external/com_google_protobuf/src/google/protobuf"
-      # add -I argument to protoc so it knows where to look for the proto files.
-      arguments += ["-I{0}".format(f + "/../..")]
-      well_known_proto_files = [f for f in ctx.attr.well_known_protos.files]
+        arguments += ["--cpp_out=" + ",".join(ctx.attr.flags) + ":" + dir_out]
+        tools = []
 
-  ctx.actions.run(
-      inputs = protos + includes + well_known_proto_files,
-      tools = tools,
-      outputs = out_files,
-      executable = ctx.executable._protoc,
-      arguments = arguments,
-  )
+    arguments += get_include_protoc_args(includes)
 
-  return struct(files=depset(out_files))
+    # Include the output directory so that protoc puts the generated code in the
+    # right directory.
+    arguments += ["--proto_path={0}{1}".format(dir_out, proto_root)]
+    arguments += [_get_srcs_file_path(proto) for proto in protos]
+
+    # create a list of well known proto files if the argument is non-None
+    well_known_proto_files = []
+    if ctx.attr.well_known_protos:
+        f = ctx.attr.well_known_protos.files.to_list()[0].dirname
+        if f != "external/com_google_protobuf/src/google/protobuf":
+            print(
+                "Error: Only @com_google_protobuf//:well_known_protos is supported",
+            )
+        else:
+            # f points to "external/com_google_protobuf/src/google/protobuf"
+            # add -I argument to protoc so it knows where to look for the proto files.
+            arguments += ["-I{0}".format(f + "/../..")]
+            well_known_proto_files = [
+                f
+                for f in ctx.attr.well_known_protos.files
+            ]
+
+    ctx.actions.run(
+        inputs = protos + includes + well_known_proto_files,
+        tools = tools,
+        outputs = out_files,
+        executable = ctx.executable._protoc,
+        arguments = arguments,
+    )
+
+    return struct(files = depset(out_files))
 
 _generate_cc = rule(
     attrs = {
@@ -96,10 +157,8 @@
             mandatory = False,
             allow_empty = True,
         ),
-        "well_known_protos" : attr.label(
-            mandatory = False,
-        ),
-        "generate_mocks" : attr.bool(
+        "well_known_protos": attr.label(mandatory = False),
+        "generate_mocks": attr.bool(
             default = False,
             mandatory = False,
         ),
@@ -115,7 +174,10 @@
 )
 
 def generate_cc(well_known_protos, **kwargs):
-  if well_known_protos:
-    _generate_cc(well_known_protos="@com_google_protobuf//:well_known_protos", **kwargs)
-  else:
-    _generate_cc(**kwargs)
+    if well_known_protos:
+        _generate_cc(
+            well_known_protos = "@com_google_protobuf//:well_known_protos",
+            **kwargs
+        )
+    else:
+        _generate_cc(**kwargs)
diff --git a/bazel/grpc_build_system.bzl b/bazel/grpc_build_system.bzl
index bc4d1f1..5f8477d 100644
--- a/bazel/grpc_build_system.bzl
+++ b/bazel/grpc_build_system.bzl
@@ -112,10 +112,7 @@
         visibility = visibility,
         testonly = testonly,
         linkopts = linkopts,
-        includes = [
-            "include",
-            "src/core/ext/upb-generated",
-        ],
+        includes = ["include"] + if_not_windows(["src/core/ext/upb-generated"]),
         alwayslink = alwayslink,
         data = data,
         tags = tags,
diff --git a/bazel/grpc_deps.bzl b/bazel/grpc_deps.bzl
index 891783b..a4e6509 100644
--- a/bazel/grpc_deps.bzl
+++ b/bazel/grpc_deps.bzl
@@ -110,6 +110,8 @@
         http_archive(
             name = "boringssl",
             # on the chromium-stable-with-bazel branch
+            # NOTE: This URL generates a tarball containing dynamic date
+            # information, so the sha256 is not consistent.
             url = "https://boringssl.googlesource.com/boringssl/+archive/afc30d43eef92979b05776ec0963c9cede5fb80f.tar.gz",
         )
 
@@ -117,6 +119,7 @@
         http_archive(
             name = "com_github_madler_zlib",
             build_file = "@com_github_grpc_grpc//third_party:zlib.BUILD",
+            sha256 = "6d4d6640ca3121620995ee255945161821218752b551a1a180f4215f7d124d45",
             strip_prefix = "zlib-cacf7f1d4e3d44d871b605da3b647f07d718623f",
             url = "https://github.com/madler/zlib/archive/cacf7f1d4e3d44d871b605da3b647f07d718623f.tar.gz",
         )
@@ -124,6 +127,7 @@
     if "com_google_protobuf" not in native.existing_rules():
         http_archive(
             name = "com_google_protobuf",
+            sha256 = "cf9e2fb1d2cd30ec9d51ff1749045208bd641f290f64b85046485934b0e03783",
             strip_prefix = "protobuf-582743bf40c5d3639a70f98f183914a2c0cd0680",
             url = "https://github.com/google/protobuf/archive/582743bf40c5d3639a70f98f183914a2c0cd0680.tar.gz",
         )
@@ -132,6 +136,7 @@
         http_archive(
             name = "com_github_nanopb_nanopb",
             build_file = "@com_github_grpc_grpc//third_party:nanopb.BUILD",
+            sha256 = "8bbbb1e78d4ddb0a1919276924ab10d11b631df48b657d960e0c795a25515735",
             strip_prefix = "nanopb-f8ac463766281625ad710900479130c7fcb4d63b",
             url = "https://github.com/nanopb/nanopb/archive/f8ac463766281625ad710900479130c7fcb4d63b.tar.gz",
         )
@@ -140,6 +145,7 @@
         http_archive(
             name = "com_github_google_googletest",
             build_file = "@com_github_grpc_grpc//third_party:gtest.BUILD",
+            sha256 = "175a22300b3450e27e5f2e6f95cc9abca74617cbc21a1e0ed19bdfbd22ea0305",
             strip_prefix = "googletest-ec44c6c1675c25b9827aacd08c02433cccde7780",
             url = "https://github.com/google/googletest/archive/ec44c6c1675c25b9827aacd08c02433cccde7780.tar.gz",
         )
@@ -147,6 +153,7 @@
     if "com_github_gflags_gflags" not in native.existing_rules():
         http_archive(
             name = "com_github_gflags_gflags",
+            sha256 = "63ae70ea3e05780f7547d03503a53de3a7d2d83ad1caaa443a31cb20aea28654",
             strip_prefix = "gflags-28f50e0fed19872e0fd50dd23ce2ee8cd759338e",
             url = "https://github.com/gflags/gflags/archive/28f50e0fed19872e0fd50dd23ce2ee8cd759338e.tar.gz",
         )
@@ -154,6 +161,7 @@
     if "com_github_google_benchmark" not in native.existing_rules():
         http_archive(
             name = "com_github_google_benchmark",
+            sha256 = "c7682e9007ddfd94072647abab3e89ffd9084089460ae47d67060974467b58bf",
             strip_prefix = "benchmark-e776aa0275e293707b6a0901e0e8d8a8a3679508",
             url = "https://github.com/google/benchmark/archive/e776aa0275e293707b6a0901e0e8d8a8a3679508.tar.gz",
         )
@@ -162,6 +170,7 @@
         http_archive(
             name = "com_github_cares_cares",
             build_file = "@com_github_grpc_grpc//third_party:cares/cares.BUILD",
+            sha256 = "e8c2751ddc70fed9dc6f999acd92e232d5846f009ee1674f8aee81f19b2b915a",
             strip_prefix = "c-ares-e982924acee7f7313b4baa4ee5ec000c5e373c30",
             url = "https://github.com/c-ares/c-ares/archive/e982924acee7f7313b4baa4ee5ec000c5e373c30.tar.gz",
         )
@@ -169,19 +178,20 @@
     if "com_google_absl" not in native.existing_rules():
         http_archive(
             name = "com_google_absl",
+            sha256 = "5fe2a3a8f8378e81d4d3db6541f48030e04233ccd2d6c7e9d981ed577b314ae8",
             strip_prefix = "abseil-cpp-308ce31528a7edfa39f5f6d36142278a0ae1bf45",
             url = "https://github.com/abseil/abseil-cpp/archive/308ce31528a7edfa39f5f6d36142278a0ae1bf45.tar.gz",
         )
 
-    if "com_github_bazelbuild_bazeltoolchains" not in native.existing_rules():
+    if "bazel_toolchains" not in native.existing_rules():
         http_archive(
-            name = "com_github_bazelbuild_bazeltoolchains",
-            strip_prefix = "bazel-toolchains-37419a124bdb9af2fec5b99a973d359b6b899b61",
+            name = "bazel_toolchains",
+            sha256 = "67335b3563d9b67dc2550b8f27cc689b64fadac491e69ce78763d9ba894cc5cc",
+            strip_prefix = "bazel-toolchains-cddc376d428ada2927ad359211c3e356bd9c9fbb",
             urls = [
-                "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/37419a124bdb9af2fec5b99a973d359b6b899b61.tar.gz",
-                "https://github.com/bazelbuild/bazel-toolchains/archive/37419a124bdb9af2fec5b99a973d359b6b899b61.tar.gz",
+                "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/cddc376d428ada2927ad359211c3e356bd9c9fbb.tar.gz",
+                "https://github.com/bazelbuild/bazel-toolchains/archive/cddc376d428ada2927ad359211c3e356bd9c9fbb.tar.gz",
             ],
-            sha256 = "ee854b5de299138c1f4a2edb5573d22b21d975acfc7aa938f36d30b49ef97498",
         )
 
     if "bazel_skylib" not in native.existing_rules():
@@ -195,6 +205,7 @@
     if "io_opencensus_cpp" not in native.existing_rules():
         http_archive(
             name = "io_opencensus_cpp",
+            sha256 = "3436ca23dc1b3345186defd0f46d64244079ba3d3234a0c5d6ef5e8d5d06c8b5",
             strip_prefix = "opencensus-cpp-9b1e354e89bf3d92aedc00af45b418ce870f3d77",
             url = "https://github.com/census-instrumentation/opencensus-cpp/archive/9b1e354e89bf3d92aedc00af45b418ce870f3d77.tar.gz",
         )
@@ -202,6 +213,7 @@
     if "upb" not in native.existing_rules():
         http_archive(
             name = "upb",
+            sha256 = "0e749a8973968397f849a3b42e28ee9c85dc418c2477954c2a6a4495f323241d",
             strip_prefix = "upb-ed9faae0993704b033c594b072d65e1bf19207fa",
             url = "https://github.com/google/upb/archive/ed9faae0993704b033c594b072d65e1bf19207fa.tar.gz",
         )
@@ -223,6 +235,7 @@
     if "com_github_twisted_twisted" not in native.existing_rules():
         http_archive(
             name = "com_github_twisted_twisted",
+            sha256 = "ca17699d0d62eafc5c28daf2c7d0a18e62ae77b4137300b6c7d7868b39b06139",
             strip_prefix = "twisted-twisted-17.5.0",
             url = "https://github.com/twisted/twisted/archive/twisted-17.5.0.zip",
             build_file = "@com_github_grpc_grpc//third_party:twisted.BUILD",
@@ -231,6 +244,7 @@
     if "com_github_yaml_pyyaml" not in native.existing_rules():
         http_archive(
             name = "com_github_yaml_pyyaml",
+            sha256 = "6b4314b1b2051ddb9d4fcd1634e1fa9c1bb4012954273c9ff3ef689f6ec6c93e",
             strip_prefix = "pyyaml-3.12",
             url = "https://github.com/yaml/pyyaml/archive/3.12.zip",
             build_file = "@com_github_grpc_grpc//third_party:yaml.BUILD",
@@ -239,6 +253,7 @@
     if "com_github_twisted_incremental" not in native.existing_rules():
         http_archive(
             name = "com_github_twisted_incremental",
+            sha256 = "f0ca93359ee70243ff7fbf2d904a6291810bd88cb80ed4aca6fa77f318a41a36",
             strip_prefix = "incremental-incremental-17.5.0",
             url = "https://github.com/twisted/incremental/archive/incremental-17.5.0.zip",
             build_file = "@com_github_grpc_grpc//third_party:incremental.BUILD",
@@ -247,6 +262,7 @@
     if "com_github_zopefoundation_zope_interface" not in native.existing_rules():
         http_archive(
             name = "com_github_zopefoundation_zope_interface",
+            sha256 = "e9579fc6149294339897be3aa9ecd8a29217c0b013fe6f44fcdae00e3204198a",
             strip_prefix = "zope.interface-4.4.3",
             url = "https://github.com/zopefoundation/zope.interface/archive/4.4.3.zip",
             build_file = "@com_github_grpc_grpc//third_party:zope_interface.BUILD",
@@ -255,6 +271,7 @@
     if "com_github_twisted_constantly" not in native.existing_rules():
         http_archive(
             name = "com_github_twisted_constantly",
+            sha256 = "2702cd322161a579d2c0dbf94af4e57712eedc7bd7bbbdc554a230544f7d346c",
             strip_prefix = "constantly-15.1.0",
             url = "https://github.com/twisted/constantly/archive/15.1.0.zip",
             build_file = "@com_github_grpc_grpc//third_party:constantly.BUILD",
diff --git a/bazel/grpc_python_deps.bzl b/bazel/grpc_python_deps.bzl
new file mode 100644
index 0000000..91438f3
--- /dev/null
+++ b/bazel/grpc_python_deps.bzl
@@ -0,0 +1,8 @@
+load("//third_party/py:python_configure.bzl", "python_configure")
+load("@io_bazel_rules_python//python:pip.bzl", "pip_repositories")
+load("@grpc_python_dependencies//:requirements.bzl", "pip_install")
+
+def grpc_python_deps():
+    python_configure(name = "local_config_python")
+    pip_repositories()
+    pip_install()
diff --git a/bazel/protobuf.bzl b/bazel/protobuf.bzl
new file mode 100644
index 0000000..f2df7bd
--- /dev/null
+++ b/bazel/protobuf.bzl
@@ -0,0 +1,104 @@
+"""Utility functions for generating protobuf code."""
+
+_PROTO_EXTENSION = ".proto"
+
+def well_known_proto_libs():
+    return [
+        "@com_google_protobuf//:any_proto",
+        "@com_google_protobuf//:api_proto",
+        "@com_google_protobuf//:compiler_plugin_proto",
+        "@com_google_protobuf//:descriptor_proto",
+        "@com_google_protobuf//:duration_proto",
+        "@com_google_protobuf//:empty_proto",
+        "@com_google_protobuf//:field_mask_proto",
+        "@com_google_protobuf//:source_context_proto",
+        "@com_google_protobuf//:struct_proto",
+        "@com_google_protobuf//:timestamp_proto",
+        "@com_google_protobuf//:type_proto",
+        "@com_google_protobuf//:wrappers_proto",
+    ]
+
+def get_proto_root(workspace_root):
+    """Gets the root protobuf directory.
+
+    Args:
+      workspace_root: context.label.workspace_root
+
+    Returns:
+      The directory relative to which generated include paths should be.
+    """
+    if workspace_root:
+        return "/{}".format(workspace_root)
+    else:
+        return ""
+
+def _strip_proto_extension(proto_filename):
+    if not proto_filename.endswith(_PROTO_EXTENSION):
+        fail('"{}" does not end with "{}"'.format(
+            proto_filename,
+            _PROTO_EXTENSION,
+        ))
+    return proto_filename[:-len(_PROTO_EXTENSION)]
+
+def proto_path_to_generated_filename(proto_path, fmt_str):
+    """Calculates the name of a generated file for a protobuf path.
+
+    For example, "examples/protos/helloworld.proto" might map to
+      "helloworld.pb.h".
+
+    Args:
+      proto_path: The path to the .proto file.
+      fmt_str: A format string used to calculate the generated filename. For
+        example, "{}.pb.h" might be used to calculate a C++ header filename.
+
+    Returns:
+      The generated filename.
+    """
+    return fmt_str.format(_strip_proto_extension(proto_path))
+
+def _get_include_directory(include):
+    directory = include.path
+    prefix_len = 0
+    if not include.is_source and directory.startswith(include.root.path):
+        prefix_len = len(include.root.path) + 1
+
+    if directory.startswith("external", prefix_len):
+        external_separator = directory.find("/", prefix_len)
+        repository_separator = directory.find("/", external_separator + 1)
+        return directory[:repository_separator]
+    else:
+        return include.root.path if include.root.path else "."
+
+def get_include_protoc_args(includes):
+    """Returns protoc args that imports protos relative to their import root.
+
+    Args:
+      includes: A list of included proto files.
+
+    Returns:
+      A list of arguments to be passed to protoc. For example, ["--proto_path=."].
+    """
+    return [
+        "--proto_path={}".format(_get_include_directory(include))
+        for include in includes
+    ]
+
+def get_plugin_args(plugin, flags, dir_out, generate_mocks):
+    """Returns arguments configuring protoc to use a plugin for a language.
+
+    Args:
+      plugin: An executable file to run as the protoc plugin.
+      flags: The plugin flags to be passed to protoc.
+      dir_out: The output directory for the plugin.
+      generate_mocks: A bool indicating whether to generate mocks.
+
+    Returns:
+      A list of protoc arguments configuring the plugin.
+    """
+    augmented_flags = list(flags)
+    if generate_mocks:
+        augmented_flags.append("generate_mock_code=true")
+    return [
+        "--plugin=protoc-gen-PLUGIN=" + plugin.path,
+        "--PLUGIN_out=" + ",".join(augmented_flags) + ":" + dir_out,
+    ]
diff --git a/bazel/python_rules.bzl b/bazel/python_rules.bzl
new file mode 100644
index 0000000..2f3b38a
--- /dev/null
+++ b/bazel/python_rules.bzl
@@ -0,0 +1,186 @@
+"""Generates and compiles Python gRPC stubs from proto_library rules."""
+
+load("@grpc_python_dependencies//:requirements.bzl", "requirement")
+load(
+    "//bazel:protobuf.bzl",
+    "get_include_protoc_args",
+    "get_plugin_args",
+    "get_proto_root",
+    "proto_path_to_generated_filename",
+)
+
+_GENERATED_PROTO_FORMAT = "{}_pb2.py"
+_GENERATED_GRPC_PROTO_FORMAT = "{}_pb2_grpc.py"
+
+def _get_staged_proto_file(context, source_file):
+    if source_file.dirname == context.label.package:
+        return source_file
+    else:
+        copied_proto = context.actions.declare_file(source_file.basename)
+        context.actions.run_shell(
+            inputs = [source_file],
+            outputs = [copied_proto],
+            command = "cp {} {}".format(source_file.path, copied_proto.path),
+            mnemonic = "CopySourceProto",
+        )
+        return copied_proto
+
+def _generate_py_impl(context):
+    protos = []
+    for src in context.attr.deps:
+        for file in src.proto.direct_sources:
+            protos.append(_get_staged_proto_file(context, file))
+    includes = [
+        file
+        for src in context.attr.deps
+        for file in src.proto.transitive_imports
+    ]
+    proto_root = get_proto_root(context.label.workspace_root)
+    format_str = (_GENERATED_GRPC_PROTO_FORMAT if context.executable.plugin else _GENERATED_PROTO_FORMAT)
+    out_files = [
+        context.actions.declare_file(
+            proto_path_to_generated_filename(
+                proto.basename,
+                format_str,
+            ),
+        )
+        for proto in protos
+    ]
+
+    arguments = []
+    tools = [context.executable._protoc]
+    if context.executable.plugin:
+        arguments += get_plugin_args(
+            context.executable.plugin,
+            context.attr.flags,
+            context.genfiles_dir.path,
+            False,
+        )
+        tools += [context.executable.plugin]
+    else:
+        arguments += [
+            "--python_out={}:{}".format(
+                ",".join(context.attr.flags),
+                context.genfiles_dir.path,
+            ),
+        ]
+
+    arguments += get_include_protoc_args(includes)
+    arguments += [
+        "--proto_path={}".format(context.genfiles_dir.path)
+        for proto in protos
+    ]
+    for proto in protos:
+        massaged_path = proto.path
+        if massaged_path.startswith(context.genfiles_dir.path):
+            massaged_path = proto.path[len(context.genfiles_dir.path) + 1:]
+        arguments.append(massaged_path)
+
+    well_known_proto_files = []
+    if context.attr.well_known_protos:
+        well_known_proto_directory = context.attr.well_known_protos.files.to_list(
+        )[0].dirname
+
+        arguments += ["-I{}".format(well_known_proto_directory + "/../..")]
+        well_known_proto_files = context.attr.well_known_protos.files.to_list()
+
+    context.actions.run(
+        inputs = protos + includes + well_known_proto_files,
+        tools = tools,
+        outputs = out_files,
+        executable = context.executable._protoc,
+        arguments = arguments,
+        mnemonic = "ProtocInvocation",
+    )
+    return struct(files = depset(out_files))
+
+__generate_py = rule(
+    attrs = {
+        "deps": attr.label_list(
+            mandatory = True,
+            allow_empty = False,
+            providers = ["proto"],
+        ),
+        "plugin": attr.label(
+            executable = True,
+            providers = ["files_to_run"],
+            cfg = "host",
+        ),
+        "flags": attr.string_list(
+            mandatory = False,
+            allow_empty = True,
+        ),
+        "well_known_protos": attr.label(mandatory = False),
+        "_protoc": attr.label(
+            default = Label("//external:protocol_compiler"),
+            executable = True,
+            cfg = "host",
+        ),
+    },
+    output_to_genfiles = True,
+    implementation = _generate_py_impl,
+)
+
+def _generate_py(well_known_protos, **kwargs):
+    if well_known_protos:
+        __generate_py(
+            well_known_protos = "@com_google_protobuf//:well_known_protos",
+            **kwargs
+        )
+    else:
+        __generate_py(**kwargs)
+
+def py_proto_library(
+        name,
+        deps,
+        well_known_protos = True,
+        proto_only = False,
+        **kwargs):
+    """Generate python code for a protobuf.
+
+    Args:
+      name: The name of the target.
+      deps: A list of dependencies. Must contain a single element.
+      well_known_protos: A bool indicating whether or not to include well-known
+        protos.
+      proto_only: A bool indicating whether to generate vanilla protobuf code
+        or to also generate gRPC code.
+    """
+    if len(deps) > 1:
+        fail("The supported length of 'deps' is 1.")
+
+    codegen_target = "_{}_codegen".format(name)
+    codegen_grpc_target = "_{}_grpc_codegen".format(name)
+
+    _generate_py(
+        name = codegen_target,
+        deps = deps,
+        well_known_protos = well_known_protos,
+        **kwargs
+    )
+
+    if not proto_only:
+        _generate_py(
+            name = codegen_grpc_target,
+            deps = deps,
+            plugin = "//:grpc_python_plugin",
+            well_known_protos = well_known_protos,
+            **kwargs
+        )
+
+        native.py_library(
+            name = name,
+            srcs = [
+                ":{}".format(codegen_grpc_target),
+                ":{}".format(codegen_target),
+            ],
+            deps = [requirement("protobuf")],
+            **kwargs
+        )
+    else:
+        native.py_library(
+            name = name,
+            srcs = [":{}".format(codegen_target), ":{}".format(codegen_target)],
+            deps = [requirement("protobuf")],
+            **kwargs
+        )
diff --git a/build.yaml b/build.yaml
index f647e42..fabb041 100644
--- a/build.yaml
+++ b/build.yaml
@@ -13,8 +13,8 @@
   '#09': Per-language overrides are possible with (eg) ruby_version tag here
   '#10': See the expand_version.py for all the quirks here
   core_version: 7.0.0
-  g_stands_for: godric
-  version: 1.20.1
+  g_stands_for: gandalf
+  version: 1.21.0-dev
 filegroups:
 - name: alts_proto
   headers:
@@ -114,7 +114,6 @@
 - name: gpr_base
   src:
   - src/core/lib/gpr/alloc.cc
-  - src/core/lib/gpr/arena.cc
   - src/core/lib/gpr/atm.cc
   - src/core/lib/gpr/cpu_iphone.cc
   - src/core/lib/gpr/cpu_linux.cc
@@ -147,7 +146,9 @@
   - src/core/lib/gpr/tmpfile_posix.cc
   - src/core/lib/gpr/tmpfile_windows.cc
   - src/core/lib/gpr/wrap_memcpy.cc
+  - src/core/lib/gprpp/arena.cc
   - src/core/lib/gprpp/fork.cc
+  - src/core/lib/gprpp/global_config_env.cc
   - src/core/lib/gprpp/thd_posix.cc
   - src/core/lib/gprpp/thd_windows.cc
   - src/core/lib/profiling/basic_timers.cc
@@ -191,11 +192,18 @@
   - src/core/lib/gpr/tmpfile.h
   - src/core/lib/gpr/useful.h
   - src/core/lib/gprpp/abstract.h
+  - src/core/lib/gprpp/arena.h
   - src/core/lib/gprpp/atomic.h
   - src/core/lib/gprpp/fork.h
+  - src/core/lib/gprpp/global_config.h
+  - src/core/lib/gprpp/global_config_custom.h
+  - src/core/lib/gprpp/global_config_env.h
+  - src/core/lib/gprpp/global_config_generic.h
   - src/core/lib/gprpp/manual_constructor.h
+  - src/core/lib/gprpp/map.h
   - src/core/lib/gprpp/memory.h
-  - src/core/lib/gprpp/mutex_lock.h
+  - src/core/lib/gprpp/pair.h
+  - src/core/lib/gprpp/sync.h
   - src/core/lib/gprpp/thd.h
   - src/core/lib/profiling/timers.h
   uses:
@@ -243,6 +251,7 @@
   - src/core/lib/channel/handshaker_registry.cc
   - src/core/lib/channel/status_util.cc
   - src/core/lib/compression/compression.cc
+  - src/core/lib/compression/compression_args.cc
   - src/core/lib/compression/compression_internal.cc
   - src/core/lib/compression/message_compress.cc
   - src/core/lib/compression/stream_compression.cc
@@ -255,12 +264,15 @@
   - src/core/lib/http/parser.cc
   - src/core/lib/iomgr/buffer_list.cc
   - src/core/lib/iomgr/call_combiner.cc
+  - src/core/lib/iomgr/cfstream_handle.cc
   - src/core/lib/iomgr/combiner.cc
   - src/core/lib/iomgr/endpoint.cc
+  - src/core/lib/iomgr/endpoint_cfstream.cc
   - src/core/lib/iomgr/endpoint_pair_posix.cc
   - src/core/lib/iomgr/endpoint_pair_uv.cc
   - src/core/lib/iomgr/endpoint_pair_windows.cc
   - src/core/lib/iomgr/error.cc
+  - src/core/lib/iomgr/error_cfstream.cc
   - src/core/lib/iomgr/ev_epoll1_linux.cc
   - src/core/lib/iomgr/ev_epollex_linux.cc
   - src/core/lib/iomgr/ev_poll_posix.cc
@@ -281,6 +293,7 @@
   - src/core/lib/iomgr/iomgr_custom.cc
   - src/core/lib/iomgr/iomgr_internal.cc
   - src/core/lib/iomgr/iomgr_posix.cc
+  - src/core/lib/iomgr/iomgr_posix_cfstream.cc
   - src/core/lib/iomgr/iomgr_uv.cc
   - src/core/lib/iomgr/iomgr_windows.cc
   - src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -309,6 +322,7 @@
   - src/core/lib/iomgr/socket_utils_windows.cc
   - src/core/lib/iomgr/socket_windows.cc
   - src/core/lib/iomgr/tcp_client.cc
+  - src/core/lib/iomgr/tcp_client_cfstream.cc
   - src/core/lib/iomgr/tcp_client_custom.cc
   - src/core/lib/iomgr/tcp_client_posix.cc
   - src/core/lib/iomgr/tcp_client_windows.cc
@@ -416,6 +430,7 @@
   - src/core/lib/channel/handshaker_registry.h
   - src/core/lib/channel/status_util.h
   - src/core/lib/compression/algorithm_metadata.h
+  - src/core/lib/compression/compression_args.h
   - src/core/lib/compression/compression_internal.h
   - src/core/lib/compression/message_compress.h
   - src/core/lib/compression/stream_compression.h
@@ -435,12 +450,15 @@
   - src/core/lib/iomgr/block_annotate.h
   - src/core/lib/iomgr/buffer_list.h
   - src/core/lib/iomgr/call_combiner.h
+  - src/core/lib/iomgr/cfstream_handle.h
   - src/core/lib/iomgr/closure.h
   - src/core/lib/iomgr/combiner.h
   - src/core/lib/iomgr/dynamic_annotations.h
   - src/core/lib/iomgr/endpoint.h
+  - src/core/lib/iomgr/endpoint_cfstream.h
   - src/core/lib/iomgr/endpoint_pair.h
   - src/core/lib/iomgr/error.h
+  - src/core/lib/iomgr/error_cfstream.h
   - src/core/lib/iomgr/error_internal.h
   - src/core/lib/iomgr/ev_epoll1_linux.h
   - src/core/lib/iomgr/ev_epollex_linux.h
@@ -541,20 +559,6 @@
   uses:
   - grpc_codegen
   - grpc_trace_headers
-- name: grpc_cfstream
-  headers:
-  - src/core/lib/iomgr/cfstream_handle.h
-  - src/core/lib/iomgr/endpoint_cfstream.h
-  - src/core/lib/iomgr/error_cfstream.h
-  src:
-  - src/core/lib/iomgr/cfstream_handle.cc
-  - src/core/lib/iomgr/endpoint_cfstream.cc
-  - src/core/lib/iomgr/error_cfstream.cc
-  - src/core/lib/iomgr/iomgr_posix_cfstream.cc
-  - src/core/lib/iomgr/tcp_client_cfstream.cc
-  uses:
-  - grpc_base_headers
-  - gpr_base_headers
 - name: grpc_client_authority_filter
   headers:
   - src/core/ext/filters/http/client_authority_filter.h
@@ -776,19 +780,24 @@
   headers:
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h
+  - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h
   src:
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc
+  - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc
+  - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc
+  - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc
   plugin: grpc_resolver_dns_ares
   uses:
   - grpc_base
   - grpc_client_channel
+  - grpc_resolver_dns_selection
 - name: grpc_resolver_dns_native
   src:
   - src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
@@ -796,6 +805,14 @@
   uses:
   - grpc_base
   - grpc_client_channel
+  - grpc_resolver_dns_selection
+- name: grpc_resolver_dns_selection
+  headers:
+  - src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h
+  src:
+  - src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc
+  uses:
+  - grpc_base
 - name: grpc_resolver_fake
   headers:
   - src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h
@@ -1246,6 +1263,7 @@
   - include/grpcpp/impl/codegen/client_interceptor.h
   - include/grpcpp/impl/codegen/client_unary_call.h
   - include/grpcpp/impl/codegen/completion_queue.h
+  - include/grpcpp/impl/codegen/completion_queue_impl.h
   - include/grpcpp/impl/codegen/completion_queue_tag.h
   - include/grpcpp/impl/codegen/config.h
   - include/grpcpp/impl/codegen/core_codegen_interface.h
@@ -1254,6 +1272,7 @@
   - include/grpcpp/impl/codegen/intercepted_channel.h
   - include/grpcpp/impl/codegen/interceptor.h
   - include/grpcpp/impl/codegen/interceptor_common.h
+  - include/grpcpp/impl/codegen/message_allocator.h
   - include/grpcpp/impl/codegen/metadata_map.h
   - include/grpcpp/impl/codegen/method_handler_impl.h
   - include/grpcpp/impl/codegen/rpc_method.h
@@ -1274,6 +1293,7 @@
   - include/grpcpp/impl/codegen/time.h
   uses:
   - grpc_codegen
+  - grpc++_internal_hdrs_only
 - name: grpc++_codegen_base_src
   language: c++
   src:
@@ -1341,16 +1361,20 @@
   - include/grpcpp/alarm.h
   - include/grpcpp/alarm_impl.h
   - include/grpcpp/channel.h
+  - include/grpcpp/channel_impl.h
   - include/grpcpp/client_context.h
   - include/grpcpp/completion_queue.h
   - include/grpcpp/create_channel.h
+  - include/grpcpp/create_channel_impl.h
   - include/grpcpp/create_channel_posix.h
   - include/grpcpp/create_channel_posix_impl.h
   - include/grpcpp/ext/health_check_service_server_builder_option.h
   - include/grpcpp/generic/async_generic_service.h
   - include/grpcpp/generic/generic_stub.h
+  - include/grpcpp/generic/generic_stub_impl.h
   - include/grpcpp/grpcpp.h
   - include/grpcpp/health_check_service_interface.h
+  - include/grpcpp/health_check_service_interface_impl.h
   - include/grpcpp/impl/call.h
   - include/grpcpp/impl/channel_argument_option.h
   - include/grpcpp/impl/client_unary_call.h
@@ -1364,25 +1388,34 @@
   - include/grpcpp/impl/server_builder_option_impl.h
   - include/grpcpp/impl/server_builder_plugin.h
   - include/grpcpp/impl/server_initializer.h
+  - include/grpcpp/impl/server_initializer_impl.h
   - include/grpcpp/impl/service_type.h
   - include/grpcpp/resource_quota.h
+  - include/grpcpp/resource_quota_impl.h
   - include/grpcpp/security/auth_context.h
   - include/grpcpp/security/auth_metadata_processor.h
+  - include/grpcpp/security/auth_metadata_processor_impl.h
   - include/grpcpp/security/credentials.h
+  - include/grpcpp/security/credentials_impl.h
   - include/grpcpp/security/server_credentials.h
+  - include/grpcpp/security/server_credentials_impl.h
   - include/grpcpp/server.h
   - include/grpcpp/server_builder.h
+  - include/grpcpp/server_builder_impl.h
   - include/grpcpp/server_context.h
+  - include/grpcpp/server_impl.h
   - include/grpcpp/server_posix.h
   - include/grpcpp/server_posix_impl.h
   - include/grpcpp/support/async_stream.h
   - include/grpcpp/support/async_unary_call.h
   - include/grpcpp/support/byte_buffer.h
   - include/grpcpp/support/channel_arguments.h
+  - include/grpcpp/support/channel_arguments_impl.h
   - include/grpcpp/support/client_callback.h
   - include/grpcpp/support/client_interceptor.h
   - include/grpcpp/support/config.h
   - include/grpcpp/support/interceptor.h
+  - include/grpcpp/support/message_allocator.h
   - include/grpcpp/support/proto_buffer_reader.h
   - include/grpcpp/support/proto_buffer_writer.h
   - include/grpcpp/support/server_callback.h
@@ -1440,6 +1473,7 @@
   - grpc_base_headers
   - grpc_transport_inproc_headers
   - grpc++_codegen_base
+  - grpc++_internal_hdrs_only
   - nanopb_headers
   - health_proto
 - name: grpc++_config_proto
@@ -1447,6 +1481,10 @@
   public_headers:
   - include/grpc++/impl/codegen/config_protobuf.h
   - include/grpcpp/impl/codegen/config_protobuf.h
+- name: grpc++_internal_hdrs_only
+  language: c++
+  public_headers:
+  - include/grpcpp/impl/codegen/sync.h
 - name: grpc++_reflection_proto
   language: c++
   src:
@@ -1650,6 +1688,13 @@
   - grpc_test_util
   - grpc
   - gpr
+- name: dns_test_util
+  build: private
+  language: c++
+  headers:
+  - test/cpp/naming/dns_test_util.h
+  src:
+  - test/cpp/naming/dns_test_util.cc
 - name: grpc++
   build: all
   language: c++
@@ -1717,6 +1762,7 @@
   public_headers:
   - include/grpc++/support/error_details.h
   - include/grpcpp/support/error_details.h
+  - include/grpcpp/support/error_details_impl.h
   src:
   - src/proto/grpc/status/status.proto
   - src/cpp/util/error_details.cc
@@ -1742,6 +1788,7 @@
   public_headers:
   - include/grpc++/ext/proto_server_reflection_plugin.h
   - include/grpcpp/ext/proto_server_reflection_plugin.h
+  - include/grpcpp/ext/proto_server_reflection_plugin_impl.h
   headers:
   - src/cpp/ext/proto_server_reflection.h
   src:
@@ -4500,6 +4547,7 @@
   src:
   - test/cpp/end2end/client_crash_test_server.cc
   deps:
+  - grpc++_test_config
   - grpc++_test_util
   - grpc_test_util
   - grpc++
@@ -4704,6 +4752,24 @@
   - grpc++
   - grpc
   - gpr
+- name: global_config_env_test
+  build: test
+  language: c++
+  src:
+  - test/core/gprpp/global_config_env_test.cc
+  deps:
+  - gpr
+  - grpc_test_util_unsecure
+  uses_polling: false
+- name: global_config_test
+  build: test
+  language: c++
+  src:
+  - test/core/gprpp/global_config_test.cc
+  deps:
+  - gpr
+  - grpc_test_util_unsecure
+  uses_polling: false
 - name: golden_file_test
   gtest: true
   build: test
@@ -4712,6 +4778,7 @@
   - src/proto/grpc/testing/compiler_test.proto
   - test/cpp/codegen/golden_file_test.cc
   deps:
+  - grpc++_test_config
   - grpc++
   - grpc
   - gpr
@@ -4739,6 +4806,22 @@
   - grpc
   - gpr
   - grpc++_test_config
+- name: grpc_core_map_test
+  gtest: true
+  build: test
+  language: c++
+  headers:
+  - test/core/gprpp/map_tester.h
+  src:
+  - test/core/gprpp/map_test.cc
+  deps:
+  - grpc_test_util
+  - grpc++
+  - grpc
+  - gpr
+  uses:
+  - grpc++_test
+  uses_polling: false
 - name: grpc_cpp_plugin
   build: protoc
   language: c++
@@ -5033,6 +5116,19 @@
   uses:
   - grpc++_test
   uses_polling: false
+- name: message_allocator_end2end_test
+  gtest: true
+  cpu_cost: 0.5
+  build: test
+  language: c++
+  src:
+  - test/cpp/end2end/message_allocator_end2end_test.cc
+  deps:
+  - grpc++_test_util
+  - grpc_test_util
+  - grpc++
+  - grpc
+  - gpr
 - name: metrics_client
   build: test
   run: false
@@ -5398,6 +5494,7 @@
   src:
   - test/cpp/end2end/server_crash_test_client.cc
   deps:
+  - grpc++_test_config
   - grpc++_test_util
   - grpc_test_util
   - grpc++
@@ -5445,6 +5542,31 @@
   - grpc++_unsecure
   - grpc_unsecure
   - gpr
+- name: service_config_end2end_test
+  gtest: true
+  build: test
+  language: c++
+  src:
+  - test/cpp/end2end/service_config_end2end_test.cc
+  deps:
+  - grpc++_test_util
+  - grpc_test_util
+  - grpc++
+  - grpc
+  - gpr
+- name: service_config_test
+  gtest: true
+  build: test
+  language: c++
+  src:
+  - test/core/client_channel/service_config_test.cc
+  deps:
+  - grpc_test_util
+  - grpc++
+  - grpc
+  - gpr
+  uses:
+  - grpc++_test
 - name: shutdown_test
   gtest: true
   build: test
diff --git a/config.m4 b/config.m4
index 2c64a3e..d90a8e9 100644
--- a/config.m4
+++ b/config.m4
@@ -45,7 +45,6 @@
     third_party/address_sorting/address_sorting_posix.c \
     third_party/address_sorting/address_sorting_windows.c \
     src/core/lib/gpr/alloc.cc \
-    src/core/lib/gpr/arena.cc \
     src/core/lib/gpr/atm.cc \
     src/core/lib/gpr/cpu_iphone.cc \
     src/core/lib/gpr/cpu_linux.cc \
@@ -78,7 +77,9 @@
     src/core/lib/gpr/tmpfile_posix.cc \
     src/core/lib/gpr/tmpfile_windows.cc \
     src/core/lib/gpr/wrap_memcpy.cc \
+    src/core/lib/gprpp/arena.cc \
     src/core/lib/gprpp/fork.cc \
+    src/core/lib/gprpp/global_config_env.cc \
     src/core/lib/gprpp/thd_posix.cc \
     src/core/lib/gprpp/thd_windows.cc \
     src/core/lib/profiling/basic_timers.cc \
@@ -97,6 +98,7 @@
     src/core/lib/channel/handshaker_registry.cc \
     src/core/lib/channel/status_util.cc \
     src/core/lib/compression/compression.cc \
+    src/core/lib/compression/compression_args.cc \
     src/core/lib/compression/compression_internal.cc \
     src/core/lib/compression/message_compress.cc \
     src/core/lib/compression/stream_compression.cc \
@@ -109,12 +111,15 @@
     src/core/lib/http/parser.cc \
     src/core/lib/iomgr/buffer_list.cc \
     src/core/lib/iomgr/call_combiner.cc \
+    src/core/lib/iomgr/cfstream_handle.cc \
     src/core/lib/iomgr/combiner.cc \
     src/core/lib/iomgr/endpoint.cc \
+    src/core/lib/iomgr/endpoint_cfstream.cc \
     src/core/lib/iomgr/endpoint_pair_posix.cc \
     src/core/lib/iomgr/endpoint_pair_uv.cc \
     src/core/lib/iomgr/endpoint_pair_windows.cc \
     src/core/lib/iomgr/error.cc \
+    src/core/lib/iomgr/error_cfstream.cc \
     src/core/lib/iomgr/ev_epoll1_linux.cc \
     src/core/lib/iomgr/ev_epollex_linux.cc \
     src/core/lib/iomgr/ev_poll_posix.cc \
@@ -135,6 +140,7 @@
     src/core/lib/iomgr/iomgr_custom.cc \
     src/core/lib/iomgr/iomgr_internal.cc \
     src/core/lib/iomgr/iomgr_posix.cc \
+    src/core/lib/iomgr/iomgr_posix_cfstream.cc \
     src/core/lib/iomgr/iomgr_uv.cc \
     src/core/lib/iomgr/iomgr_windows.cc \
     src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -163,6 +169,7 @@
     src/core/lib/iomgr/socket_utils_windows.cc \
     src/core/lib/iomgr/socket_windows.cc \
     src/core/lib/iomgr/tcp_client.cc \
+    src/core/lib/iomgr/tcp_client_cfstream.cc \
     src/core/lib/iomgr/tcp_client_custom.cc \
     src/core/lib/iomgr/tcp_client_posix.cc \
     src/core/lib/iomgr/tcp_client_windows.cc \
@@ -396,12 +403,16 @@
     src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc \
+    src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc \
     src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc \
     src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc \
     src/core/ext/filters/census/grpc_context.cc \
@@ -685,6 +696,7 @@
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/lb_policy/pick_first)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/lb_policy/round_robin)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/lb_policy/xds)
+  PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/resolver/dns)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/resolver/dns/c_ares)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/resolver/dns/native)
   PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/client_channel/resolver/fake)
diff --git a/config.w32 b/config.w32
index 6897c10..70ac245 100644
--- a/config.w32
+++ b/config.w32
@@ -20,7 +20,6 @@
     "third_party\\address_sorting\\address_sorting_posix.c " +
     "third_party\\address_sorting\\address_sorting_windows.c " +
     "src\\core\\lib\\gpr\\alloc.cc " +
-    "src\\core\\lib\\gpr\\arena.cc " +
     "src\\core\\lib\\gpr\\atm.cc " +
     "src\\core\\lib\\gpr\\cpu_iphone.cc " +
     "src\\core\\lib\\gpr\\cpu_linux.cc " +
@@ -53,7 +52,9 @@
     "src\\core\\lib\\gpr\\tmpfile_posix.cc " +
     "src\\core\\lib\\gpr\\tmpfile_windows.cc " +
     "src\\core\\lib\\gpr\\wrap_memcpy.cc " +
+    "src\\core\\lib\\gprpp\\arena.cc " +
     "src\\core\\lib\\gprpp\\fork.cc " +
+    "src\\core\\lib\\gprpp\\global_config_env.cc " +
     "src\\core\\lib\\gprpp\\thd_posix.cc " +
     "src\\core\\lib\\gprpp\\thd_windows.cc " +
     "src\\core\\lib\\profiling\\basic_timers.cc " +
@@ -72,6 +73,7 @@
     "src\\core\\lib\\channel\\handshaker_registry.cc " +
     "src\\core\\lib\\channel\\status_util.cc " +
     "src\\core\\lib\\compression\\compression.cc " +
+    "src\\core\\lib\\compression\\compression_args.cc " +
     "src\\core\\lib\\compression\\compression_internal.cc " +
     "src\\core\\lib\\compression\\message_compress.cc " +
     "src\\core\\lib\\compression\\stream_compression.cc " +
@@ -84,12 +86,15 @@
     "src\\core\\lib\\http\\parser.cc " +
     "src\\core\\lib\\iomgr\\buffer_list.cc " +
     "src\\core\\lib\\iomgr\\call_combiner.cc " +
+    "src\\core\\lib\\iomgr\\cfstream_handle.cc " +
     "src\\core\\lib\\iomgr\\combiner.cc " +
     "src\\core\\lib\\iomgr\\endpoint.cc " +
+    "src\\core\\lib\\iomgr\\endpoint_cfstream.cc " +
     "src\\core\\lib\\iomgr\\endpoint_pair_posix.cc " +
     "src\\core\\lib\\iomgr\\endpoint_pair_uv.cc " +
     "src\\core\\lib\\iomgr\\endpoint_pair_windows.cc " +
     "src\\core\\lib\\iomgr\\error.cc " +
+    "src\\core\\lib\\iomgr\\error_cfstream.cc " +
     "src\\core\\lib\\iomgr\\ev_epoll1_linux.cc " +
     "src\\core\\lib\\iomgr\\ev_epollex_linux.cc " +
     "src\\core\\lib\\iomgr\\ev_poll_posix.cc " +
@@ -110,6 +115,7 @@
     "src\\core\\lib\\iomgr\\iomgr_custom.cc " +
     "src\\core\\lib\\iomgr\\iomgr_internal.cc " +
     "src\\core\\lib\\iomgr\\iomgr_posix.cc " +
+    "src\\core\\lib\\iomgr\\iomgr_posix_cfstream.cc " +
     "src\\core\\lib\\iomgr\\iomgr_uv.cc " +
     "src\\core\\lib\\iomgr\\iomgr_windows.cc " +
     "src\\core\\lib\\iomgr\\is_epollexclusive_available.cc " +
@@ -138,6 +144,7 @@
     "src\\core\\lib\\iomgr\\socket_utils_windows.cc " +
     "src\\core\\lib\\iomgr\\socket_windows.cc " +
     "src\\core\\lib\\iomgr\\tcp_client.cc " +
+    "src\\core\\lib\\iomgr\\tcp_client_cfstream.cc " +
     "src\\core\\lib\\iomgr\\tcp_client_custom.cc " +
     "src\\core\\lib\\iomgr\\tcp_client_posix.cc " +
     "src\\core\\lib\\iomgr\\tcp_client_windows.cc " +
@@ -371,12 +378,16 @@
     "src\\core\\ext\\filters\\client_channel\\lb_policy\\round_robin\\round_robin.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\dns_resolver_ares.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_ev_driver.cc " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_ev_driver_libuv.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_ev_driver_posix.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_ev_driver_windows.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper_fallback.cc " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper_libuv.cc " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper_libuv_windows.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper_posix.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper_windows.cc " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\dns_resolver_selection.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\native\\dns_resolver.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\sockaddr\\sockaddr_resolver.cc " +
     "src\\core\\ext\\filters\\census\\grpc_context.cc " +
diff --git a/doc/PROTOCOL-HTTP2.md b/doc/PROTOCOL-HTTP2.md
index a354dad..e73e682 100644
--- a/doc/PROTOCOL-HTTP2.md
+++ b/doc/PROTOCOL-HTTP2.md
@@ -61,6 +61,8 @@
 
 If **Timeout** is omitted a server should assume an infinite timeout. Client implementations are free to send a default minimum timeout based on their deployment requirements.
 
+If **Content-Type** does not begin with "application/grpc", gRPC servers SHOULD respond with HTTP status of 415 (Unsupported Media Type).  This will prevent other HTTP/2 clients from interpreting a gRPC error response, which uses status 200 (OK), as successful.
+
 **Custom-Metadata** is an arbitrary set of key-value pairs defined by the application layer. Header names starting with "grpc-" but not listed here are reserved for future GRPC use and should not be used by applications as **Custom-Metadata**.
 
 Note that HTTP2 does not allow arbitrary octet sequences for header values so binary header values must be encoded using Base64 as per https://tools.ietf.org/html/rfc4648#section-4. Implementations MUST accept padded and un-padded values and should emit un-padded values. Applications define binary headers by having their names end with "-bin". Runtime libraries use this suffix to detect binary headers and properly apply base64 encoding & decoding as headers are sent and received.
@@ -255,5 +257,3 @@
 * **Service-Name** → ?( {_proto package name_} "." ) {_service name_}
 * **Message-Type** → {_fully qualified proto message name_}
 * **Content-Type** → "application/grpc+proto"
-
-
diff --git a/doc/core/grpc-polling-engines.md b/doc/core/grpc-polling-engines.md
index dd5a765..f93f72b 100644
--- a/doc/core/grpc-polling-engines.md
+++ b/doc/core/grpc-polling-engines.md
@@ -135,7 +135,7 @@
   - The same FD can be in multiple `Pollable`s (even if one of the `Pollable`s is of type PO_FD)
   - There cannot be two `Pollable`s of type PO_FD for the same fd
 
-- Why do we need `Pollable` of type PO_FD and PO_EMTPY ?
+- Why do we need `Pollable` of type PO_FD and PO_EMPTY ?
   - The main reason is the Sync client API
     - We create one new completion queue per call. If we didn’t have PO_EMPTY and PO_FD type pollables, then every call on a given channel will effectively have to create a `Pollable` and hence an epollset. This is because every completion queue automatically creates a pollset and the channel fd will have to be put in that pollset. This clearly requires an epollset to put that fd. Creating an epollset per call (even if we delete the epollset once the call is completed) would mean a lot of sys calls to create/delete epoll fds. This is clearly not a good idea.
     - With these new types of `Pollable`s, all pollsets (corresponding to the new per-call completion queue) will initially point to PO_EMPTY global epollset. Then once the channel fd is added to the pollset, the pollset will point to the `Pollable` of type PO_FD containing just that fd (i.e it will reuse the existing `Pollable`). This way, the epoll fd creation/deletion churn is avoided.
diff --git a/doc/environment_variables.md b/doc/environment_variables.md
index e8d0dbd..778560d 100644
--- a/doc/environment_variables.md
+++ b/doc/environment_variables.md
@@ -50,6 +50,7 @@
     resolver and load balancing policy interaction
   - compression - traces compression operations
   - connectivity_state - traces connectivity state changes to channels
+  - cronet - traces state in the cronet transport engine
   - executor - traces grpc's internal thread pool ('the executor')
   - fd_trace - traces fd create(), shutdown() and close() calls for channel fds.
     Also traces epoll fd create()/close() calls in epollex polling engine
@@ -145,13 +146,3 @@
 * grpc_cfstream
   set to 1 to turn on CFStream experiment. With this experiment gRPC uses CFStream API to make TCP
   connections. The option is only available on iOS platform and when macro GRPC_CFSTREAM is defined.
-
-* GRPC_ARENA_INIT_STRATEGY
-  Selects the initialization strategy for blocks allocated in the arena. Valid
-  values are:
-  - no_init (default): Do not initialize the arena block.
-  - zero_init: Initialize the arena blocks with 0.
-  - non_zero_init: Initialize the arena blocks with a non-zero value.
-
-  NOTE: This environment variable is experimental and will be removed. Thus, it
-        should not be relied upon.
diff --git a/doc/g_stands_for.md b/doc/g_stands_for.md
index 5c5579d..b926db1 100644
--- a/doc/g_stands_for.md
+++ b/doc/g_stands_for.md
@@ -20,3 +20,4 @@
 - 1.18 'g' stands for ['goose'](https://github.com/grpc/grpc/tree/v1.18.x)
 - 1.19 'g' stands for ['gold'](https://github.com/grpc/grpc/tree/v1.19.x)
 - 1.20 'g' stands for ['godric'](https://github.com/grpc/grpc/tree/v1.20.x)
+- 1.21 'g' stands for ['gandalf'](https://github.com/grpc/grpc/tree/master)
diff --git a/doc/python/sphinx/grpc.rst b/doc/python/sphinx/grpc.rst
index f534d25..0934db7 100644
--- a/doc/python/sphinx/grpc.rst
+++ b/doc/python/sphinx/grpc.rst
@@ -172,3 +172,9 @@
 .. autoexception:: FutureTimeoutError
 .. autoexception:: FutureCancelledError
 .. autoclass:: Future
+
+
+Compression
+^^^^^^^^^^^
+
+.. autoclass:: Compression
diff --git a/doc/statuscodes.md b/doc/statuscodes.md
index 3d4d87e..61e0d82 100644
--- a/doc/statuscodes.md
+++ b/doc/statuscodes.md
@@ -20,7 +20,7 @@
 | OUT_OF_RANGE | 11 | The operation was attempted past the valid range. E.g., seeking or reading past end-of-file. Unlike `INVALID_ARGUMENT`, this error indicates a problem that may be fixed if the system state changes. For example, a 32-bit file system will generate `INVALID_ARGUMENT` if asked to read at an offset that is not in the range [0,2^32-1], but it will generate `OUT_OF_RANGE` if asked to read from an offset past the current file size. There is a fair bit of overlap between `FAILED_PRECONDITION` and `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific error) when it applies so that callers who are iterating through a space can easily look for an `OUT_OF_RANGE` error to detect when they are done. | 400 Bad Request |
 | UNIMPLEMENTED | 12 | The operation is not implemented or is not supported/enabled in this service. | 501 Not Implemented |
 | INTERNAL | 13 | Internal errors. This means that some invariants expected by the underlying system have been broken. This error code is reserved for serious errors. | 500 Internal Server Error |
-| UNAVAILABLE | 14 | The service is currently unavailable. This is most likely a transient condition, which can be corrected by retrying with a backoff. | 503 Service Unavailable |
+| UNAVAILABLE | 14 | The service is currently unavailable. This is most likely a transient condition, which can be corrected by retrying with a backoff. Note that it is not always safe to retry non-idempotent operations. | 503 Service Unavailable |
 | DATA_LOSS | 15 | Unrecoverable data loss or corruption. | 500 Internal Server Error |
 
 All RPCs started at a client return a `status` object composed of an integer
diff --git a/etc/roots.pem b/etc/roots.pem
index be59871..22bd2ab 100644
--- a/etc/roots.pem
+++ b/etc/roots.pem
@@ -4552,3 +4552,149 @@
 jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
 3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
 -----END CERTIFICATE-----
+
+# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
+# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
+# Label: "emSign Root CA - G1"
+# Serial: 235931866688319308814040
+# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
+# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
+# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
+-----BEGIN CERTIFICATE-----
+MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
+VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
+ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
+MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
+MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
+Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
+f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
+8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
+d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
+tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
+Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
+o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
+PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
+wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
+GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
+6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
+RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
+iN66zB+Afko=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
+# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
+# Label: "emSign ECC Root CA - G3"
+# Serial: 287880440101571086945156
+# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
+# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
+# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
+-----BEGIN CERTIFICATE-----
+MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
+EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
+bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
+RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
+TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
+b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
+djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
+WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
+fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
+zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
+hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
+CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
++JbNR6iC8hZVdyR+EhCVBCyj
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
+# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
+# Label: "emSign Root CA - C1"
+# Serial: 825510296613316004955058
+# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
+# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
+# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
+-----BEGIN CERTIFICATE-----
+MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
+A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
+SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
+MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
+biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
+dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
+BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
+HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
+3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
+GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
+xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
+aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
+TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
+/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
+kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
+YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
+WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
+# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
+# Label: "emSign ECC Root CA - C3"
+# Serial: 582948710642506000014504
+# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
+# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
+# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
+-----BEGIN CERTIFICATE-----
+MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
+EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
+IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
+MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
+biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
+IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
+MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
+sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
+BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
+Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
+3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
+0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
+# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
+# Label: "Hongkong Post Root CA 3"
+# Serial: 46170865288971385588281144162979347873371282084
+# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
+# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
+# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
+-----BEGIN CERTIFICATE-----
+MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
+BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
+SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
+a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
+NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
+CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
+Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
+dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
+VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
+9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
+2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
+vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
+bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
+x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
+l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
+TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
+Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
+i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
+DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
+7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
+MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
+gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
+GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
+3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
+Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
+l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
+JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
+L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
+LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
+mpv0
+-----END CERTIFICATE-----
diff --git a/examples/BUILD b/examples/BUILD
index d2b39b8..a9dd949 100644
--- a/examples/BUILD
+++ b/examples/BUILD
@@ -16,9 +16,9 @@
 
 package(default_visibility = ["//visibility:public"])
 
-load("@grpc_python_dependencies//:requirements.bzl", "requirement")
 load("//bazel:grpc_build_system.bzl", "grpc_proto_library")
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
+load("//bazel:cc_grpc_library.bzl", "cc_grpc_library")
+load("//bazel:python_rules.bzl", "py_proto_library")
 
 grpc_proto_library(
     name = "auth_sample",
@@ -30,11 +30,25 @@
     srcs = ["protos/hellostreamingworld.proto"],
 )
 
-grpc_proto_library(
-    name = "helloworld",
+# The following three rules demonstrate the usage of the cc_grpc_library rule in
+# in a mode compatible with the native proto_library and cc_proto_library rules.
+proto_library(
+    name = "helloworld_proto",
     srcs = ["protos/helloworld.proto"],
 )
 
+cc_proto_library(
+    name = "helloworld_cc_proto",
+    deps = [":helloworld_proto"],
+)
+
+cc_grpc_library(
+    name = "helloworld_cc_grpc",
+    srcs = [":helloworld_proto"],
+    grpc_only = True,
+    deps = [":helloworld_cc_proto"],
+)
+
 grpc_proto_library(
     name = "route_guide",
     srcs = ["protos/route_guide.proto"],
@@ -45,11 +59,14 @@
     srcs = ["protos/keyvaluestore.proto"],
 )
 
+proto_library(
+    name = "helloworld_proto_descriptor",
+    srcs = ["protos/helloworld.proto"],
+)
+
 py_proto_library(
     name = "py_helloworld",
-    protos = ["protos/helloworld.proto"],
-    with_grpc = True,
-    deps = [requirement('protobuf'),],
+    deps = [":helloworld_proto_descriptor"],
 )
 
 cc_binary(
@@ -57,7 +74,7 @@
     srcs = ["cpp/helloworld/greeter_client.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -67,7 +84,7 @@
     srcs = ["cpp/helloworld/greeter_async_client.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -77,7 +94,7 @@
     srcs = ["cpp/helloworld/greeter_async_client2.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -87,7 +104,7 @@
     srcs = ["cpp/helloworld/greeter_server.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -97,7 +114,7 @@
     srcs = ["cpp/helloworld/greeter_async_server.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -107,7 +124,7 @@
     srcs = ["cpp/metadata/greeter_client.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -117,7 +134,7 @@
     srcs = ["cpp/metadata/greeter_server.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -127,7 +144,7 @@
     srcs = ["cpp/load_balancing/greeter_client.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -137,7 +154,7 @@
     srcs = ["cpp/load_balancing/greeter_server.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -147,7 +164,7 @@
     srcs = ["cpp/compression/greeter_client.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
@@ -157,15 +174,17 @@
     srcs = ["cpp/compression/greeter_server.cc"],
     defines = ["BAZEL_BUILD"],
     deps = [
-        ":helloworld",
+        ":helloworld_cc_grpc",
         "//:grpc++",
     ],
 )
 
 cc_binary(
     name = "keyvaluestore_client",
-    srcs = ["cpp/keyvaluestore/caching_interceptor.h",
-            "cpp/keyvaluestore/client.cc"],    
+    srcs = [
+        "cpp/keyvaluestore/caching_interceptor.h",
+        "cpp/keyvaluestore/client.cc",
+    ],
     defines = ["BAZEL_BUILD"],
     deps = [
         ":keyvaluestore",
diff --git a/examples/cpp/route_guide/route_guide_server.cc b/examples/cpp/route_guide/route_guide_server.cc
index 7816427..4923966 100644
--- a/examples/cpp/route_guide/route_guide_server.cc
+++ b/examples/cpp/route_guide/route_guide_server.cc
@@ -151,24 +151,25 @@
 
   Status RouteChat(ServerContext* context,
                    ServerReaderWriter<RouteNote, RouteNote>* stream) override {
-    std::vector<RouteNote> received_notes;
     RouteNote note;
     while (stream->Read(&note)) {
-      for (const RouteNote& n : received_notes) {
+      std::unique_lock<std::mutex> lock(mu_);
+      for (const RouteNote& n : received_notes_) {
         if (n.location().latitude() == note.location().latitude() &&
             n.location().longitude() == note.location().longitude()) {
           stream->Write(n);
         }
       }
-      received_notes.push_back(note);
+      received_notes_.push_back(note);
     }
 
     return Status::OK;
   }
 
  private:
-
   std::vector<Feature> feature_list_;
+  std::mutex mu_;
+  std::vector<RouteNote> received_notes_;
 };
 
 void RunServer(const std::string& db_path) {
diff --git a/examples/csharp/HelloworldXamarin/iOS/AppDelegate.cs b/examples/csharp/HelloworldXamarin/iOS/AppDelegate.cs
index e7620f9..16a071c 100644
--- a/examples/csharp/HelloworldXamarin/iOS/AppDelegate.cs
+++ b/examples/csharp/HelloworldXamarin/iOS/AppDelegate.cs
@@ -53,7 +53,7 @@
         public override void DidEnterBackground(UIApplication application)
         {
             // Use this method to release shared resources, save user data, invalidate timers and store the application state.
-            // If your application supports background exection this method is called instead of WillTerminate when the user quits.
+            // If your application supports background execution this method is called instead of WillTerminate when the user quits.
         }
 
         public override void WillEnterForeground(UIApplication application)
diff --git a/examples/objective-c/auth_sample/MakeRPCViewController.m b/examples/objective-c/auth_sample/MakeRPCViewController.m
index 545deb5..648bbab 100644
--- a/examples/objective-c/auth_sample/MakeRPCViewController.m
+++ b/examples/objective-c/auth_sample/MakeRPCViewController.m
@@ -46,8 +46,16 @@
 }
 @end
 
+@interface MakeRPCViewController ()<GRPCProtoResponseHandler>
+
+@end
+
 @implementation MakeRPCViewController
 
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
+
 - (void)viewWillAppear:(BOOL)animated {
 
   // Create a service client and a proto request as usual.
@@ -57,28 +65,30 @@
   request.fillUsername = YES;
   request.fillOauthScope = YES;
 
-  // Create a not-yet-started RPC. We want to set the request headers on this object before starting
-  // it.
-  ProtoRPC *call =
-      [client RPCToUnaryCallWithRequest:request handler:^(AUTHResponse *response, NSError *error) {
-        if (response) {
-          // This test server responds with the email and scope of the access token it receives.
-          self.mainLabel.text = [NSString stringWithFormat:@"Used scope: %@ on behalf of user %@",
-                                 response.oauthScope, response.username];
-
-        } else {
-          self.mainLabel.text = error.UIDescription;
-        }
-      }];
-
-  // Set the access token to be used.
-  NSString *accessToken = GIDSignIn.sharedInstance.currentUser.authentication.accessToken;
-  call.requestHeaders[@"Authorization"] = [@"Bearer " stringByAppendingString:accessToken];
-
-  // Start the RPC.
+  // Set the request header with call options
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.oauth2AccessToken = GIDSignIn.sharedInstance.currentUser.authentication.accessToken;
+  GRPCUnaryProtoCall *call = [client unaryCallWithMessage:request
+                                          responseHandler:self
+                                              callOptions:options];
   [call start];
 
   self.mainLabel.text = @"Waiting for RPC to complete...";
 }
 
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  AUTHResponse *response = (AUTHResponse *)message;
+  if (response) {
+    // This test server responds with the email and scope of the access token it receives.
+    self.mainLabel.text = [NSString stringWithFormat:@"Used scope: %@ on behalf of user %@",
+                           response.oauthScope, response.username];
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (error) {
+    self.mainLabel.text = error.UIDescription;
+  }
+}
+
 @end
diff --git a/examples/objective-c/helloworld/main.m b/examples/objective-c/helloworld/main.m
index c771375..649e65b 100644
--- a/examples/objective-c/helloworld/main.m
+++ b/examples/objective-c/helloworld/main.m
@@ -25,20 +25,41 @@
 
 static NSString * const kHostAddress = @"localhost:50051";
 
+@interface HLWResponseHandler : NSObject<GRPCProtoResponseHandler>
+
+@end
+
+// A response handler object dispatching messages to main queue
+@implementation HLWResponseHandler
+
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
+
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  NSLog(@"%@", message);
+}
+
+@end
+
 int main(int argc, char * argv[]) {
   @autoreleasepool {
-    [GRPCCall useInsecureConnectionsForHost:kHostAddress];
-    [GRPCCall setUserAgentPrefix:@"HelloWorld/1.0" forHost:kHostAddress];
-
     HLWGreeter *client = [[HLWGreeter alloc] initWithHost:kHostAddress];
 
     HLWHelloRequest *request = [HLWHelloRequest message];
     request.name = @"Objective-C";
 
-    [client sayHelloWithRequest:request handler:^(HLWHelloReply *response, NSError *error) {
-      NSLog(@"%@", response.message);
-    }];
-    
+    GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+    // this example does not use TLS (secure channel); use insecure channel instead
+    options.transportType = GRPCTransportTypeInsecure;
+    options.userAgentPrefix = @"HelloWorld/1.0";
+
+    GRPCUnaryProtoCall *call = [client sayHelloWithMessage:request
+                                           responseHandler:[[HLWResponseHandler alloc] init]
+                                               callOptions:options];
+
+    [call start];
+
     return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
 }
diff --git a/examples/objective-c/helloworld_macos/main.m b/examples/objective-c/helloworld_macos/main.m
index 2a98ec4..ce24a9b 100644
--- a/examples/objective-c/helloworld_macos/main.m
+++ b/examples/objective-c/helloworld_macos/main.m
@@ -24,19 +24,40 @@
 
 static NSString * const kHostAddress = @"localhost:50051";
 
+@interface HLWResponseHandler : NSObject<GRPCProtoResponseHandler>
+
+@end
+
+// A response handler object dispatching messages to main queue
+@implementation HLWResponseHandler
+
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
+
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  NSLog(@"%@", message);
+}
+
+@end
+
 int main(int argc, const char * argv[]) {
   @autoreleasepool {
-    [GRPCCall useInsecureConnectionsForHost:kHostAddress];
-    [GRPCCall setUserAgentPrefix:@"HelloWorld/1.0" forHost:kHostAddress];
-
     HLWGreeter *client = [[HLWGreeter alloc] initWithHost:kHostAddress];
 
     HLWHelloRequest *request = [HLWHelloRequest message];
     request.name = @"Objective-C";
 
-    [client sayHelloWithRequest:request handler:^(HLWHelloReply *response, NSError *error) {
-      NSLog(@"%@", response.message);
-    }];
+    GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+    // this example does not use TLS (secure channel); use insecure channel instead
+    options.transportType = GRPCTransportTypeInsecure;
+    options.userAgentPrefix = @"HelloWorld/1.0";
+
+    GRPCUnaryProtoCall *call = [client sayHelloWithMessage:request
+                                           responseHandler:[[HLWResponseHandler alloc] init]
+                                               callOptions:options];
+
+    [call start];
   }
 
   return NSApplicationMain(argc, argv);
diff --git a/examples/objective-c/route_guide/ViewControllers.m b/examples/objective-c/route_guide/ViewControllers.m
index 0f116f3..43d2082 100644
--- a/examples/objective-c/route_guide/ViewControllers.m
+++ b/examples/objective-c/route_guide/ViewControllers.m
@@ -17,10 +17,7 @@
  */
 
 #import <UIKit/UIKit.h>
-#import <GRPCClient/GRPCCall+Tests.h>
 #import <RouteGuide/RouteGuide.pbrpc.h>
-#import <RxLibrary/GRXWriter+Immediate.h>
-#import <RxLibrary/GRXWriter+Transformations.h>
 
 static NSString * const kHostAddress = @"localhost:50051";
 
@@ -65,7 +62,7 @@
  * Run the getFeature demo. Calls getFeature with a point known to have a feature and a point known
  * not to have a feature.
  */
-@interface GetFeatureViewController : UIViewController
+@interface GetFeatureViewController : UIViewController<GRPCProtoResponseHandler>
 
 @property (weak, nonatomic) IBOutlet UILabel *outputLabel;
 
@@ -75,39 +72,56 @@
   RTGRouteGuide *_service;
 }
 
-- (void)execRequest {
-  void (^handler)(RTGFeature *response, NSError *error) = ^(RTGFeature *response, NSError *error) {
-    // TODO(makdharma): Remove boilerplate by consolidating into one log function.
-    if (response.name.length) {
-      NSString *str =[NSString stringWithFormat:@"%@\nFound feature called %@ at %@.", self.outputLabel.text, response.location, response.name];
-      self.outputLabel.text = str;
-      NSLog(@"Found feature called %@ at %@.", response.name, response.location);
-    } else if (response) {
-      NSString *str =[NSString stringWithFormat:@"%@\nFound no features at %@",  self.outputLabel.text,response.location];
-      self.outputLabel.text = str;
-      NSLog(@"Found no features at %@", response.location);
-    } else {
-      NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
-      self.outputLabel.text = str;
-      NSLog(@"RPC error: %@", error);
-    }
-  };
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
 
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  RTGFeature *response = (RTGFeature *)message;
+
+  // TODO(makdharma): Remove boilerplate by consolidating into one log function.
+  if (response.name.length != 0) {
+    NSString *str =[NSString stringWithFormat:@"%@\nFound feature called %@ at %@.", self.outputLabel.text, response.location, response.name];
+    self.outputLabel.text = str;
+    NSLog(@"Found feature called %@ at %@.", response.name, response.location);
+  } else if (response) {
+    NSString *str =[NSString stringWithFormat:@"%@\nFound no features at %@",  self.outputLabel.text,response.location];
+    self.outputLabel.text = str;
+    NSLog(@"Found no features at %@", response.location);
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (error) {
+    NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
+    self.outputLabel.text = str;
+    NSLog(@"RPC error: %@", error);
+  }
+}
+
+- (void)execRequest {
   RTGPoint *point = [RTGPoint message];
   point.latitude = 409146138;
   point.longitude = -746188906;
 
-  [_service getFeatureWithRequest:point handler:handler];
-  [_service getFeatureWithRequest:[RTGPoint message] handler:handler];
+  GRPCUnaryProtoCall *call = [_service getFeatureWithMessage:point
+                                             responseHandler:self
+                                                 callOptions:nil];
+  [call start];
+  call = [_service getFeatureWithMessage:[RTGPoint message]
+                         responseHandler:self
+                             callOptions:nil];
+  [call start];
+
 }
 
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  // This only needs to be done once per host, before creating service objects for that host.
-  [GRPCCall useInsecureConnectionsForHost:kHostAddress];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
 
-  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
+  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress callOptions:options];
 }
 
 - (void)viewDidAppear:(BOOL)animated {
@@ -126,7 +140,7 @@
  * Run the listFeatures demo. Calls listFeatures with a rectangle containing all of the features in
  * the pre-generated database. Prints each response as it comes in.
  */
-@interface ListFeaturesViewController : UIViewController
+@interface ListFeaturesViewController : UIViewController<GRPCProtoResponseHandler>
 
 @property (weak, nonatomic) IBOutlet UILabel *outputLabel;
 
@@ -136,6 +150,10 @@
   RTGRouteGuide *_service;
 }
 
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
+
 - (void)execRequest {
   RTGRectangle *rectangle = [RTGRectangle message];
   rectangle.lo.latitude = 405E6;
@@ -144,24 +162,36 @@
   rectangle.hi.longitude = -745E6;
 
   NSLog(@"Looking for features between %@ and %@", rectangle.lo, rectangle.hi);
-  [_service listFeaturesWithRequest:rectangle
-                      eventHandler:^(BOOL done, RTGFeature *response, NSError *error) {
-    if (response) {
-      NSString *str =[NSString stringWithFormat:@"%@\nFound feature at %@ called %@.", self.outputLabel.text, response.location, response.name];
-      self.outputLabel.text = str;
-      NSLog(@"Found feature at %@ called %@.", response.location, response.name);
-    } else if (error) {
-      NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
-      self.outputLabel.text = str;
-      NSLog(@"RPC error: %@", error);
-    }
-  }];
+  GRPCUnaryProtoCall *call = [_service listFeaturesWithMessage:rectangle
+                                               responseHandler:self
+                                                   callOptions:nil];
+  [call start];
+}
+
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  RTGFeature *response = (RTGFeature *)message;
+  if (response) {
+    NSString *str =[NSString stringWithFormat:@"%@\nFound feature at %@ called %@.", self.outputLabel.text, response.location, response.name];
+    self.outputLabel.text = str;
+    NSLog(@"Found feature at %@ called %@.", response.location, response.name);
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (error) {
+    NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
+    self.outputLabel.text = str;
+    NSLog(@"RPC error: %@", error);
+  }
 }
 
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+
+  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress callOptions:options];
 }
 
 - (void)viewDidAppear:(BOOL)animated {
@@ -173,7 +203,6 @@
 
 @end
 
-
 #pragma mark Demo: Record Route
 
 /**
@@ -181,7 +210,7 @@
  * database with a variable delay in between. Prints the statistics when they are sent from the
  * server.
  */
-@interface RecordRouteViewController : UIViewController
+@interface RecordRouteViewController : UIViewController<GRPCProtoResponseHandler>
 
 @property (weak, nonatomic) IBOutlet UILabel *outputLabel;
 
@@ -191,47 +220,71 @@
   RTGRouteGuide *_service;
 }
 
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
+
 - (void)execRequest {
   NSString *dataBasePath = [NSBundle.mainBundle pathForResource:@"route_guide_db"
                                                          ofType:@"json"];
   NSData *dataBaseContent = [NSData dataWithContentsOfFile:dataBasePath];
-  NSArray *features = [NSJSONSerialization JSONObjectWithData:dataBaseContent options:0 error:NULL];
+  NSError *error;
+  NSArray *features = [NSJSONSerialization JSONObjectWithData:dataBaseContent options:0 error:&error];
 
-  GRXWriter *locations = [[GRXWriter writerWithContainer:features] map:^id(id feature) {
+  if (error) {
+    NSLog(@"Error reading database.");
+    NSString *str = @"Error reading database.";
+    self.outputLabel.text = str;
+    return;
+  }
+
+  GRPCStreamingProtoCall *call = [_service recordRouteWithResponseHandler:self
+                                                              callOptions:nil];
+  [call start];
+  for (id feature in features) {
     RTGPoint *location = [RTGPoint message];
     location.longitude = [((NSNumber *) feature[@"location"][@"longitude"]) intValue];
     location.latitude = [((NSNumber *) feature[@"location"][@"latitude"]) intValue];
     NSString *str =[NSString stringWithFormat:@"%@\nVisiting point %@", self.outputLabel.text, location];
     self.outputLabel.text = str;
     NSLog(@"Visiting point %@", location);
-    return location;
-  }];
+    [call writeMessage:location];
+  }
+  [call finish];
+}
 
-  [_service recordRouteWithRequestsWriter:locations
-                                 handler:^(RTGRouteSummary *response, NSError *error) {
-    if (response) {
-      NSString *str =[NSString stringWithFormat:
-                      @"%@\nFinished trip with %i points\nPassed %i features\n"
-                      "Travelled %i meters\nIt took %i seconds",
-                      self.outputLabel.text, response.pointCount, response.featureCount,
-                      response.distance, response.elapsedTime];
-      self.outputLabel.text = str;
-      NSLog(@"Finished trip with %i points", response.pointCount);
-      NSLog(@"Passed %i features", response.featureCount);
-      NSLog(@"Travelled %i meters", response.distance);
-      NSLog(@"It took %i seconds", response.elapsedTime);
-    } else {
-      NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
-      self.outputLabel.text = str;
-      NSLog(@"RPC error: %@", error);
-    }
-  }];
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  RTGRouteSummary *response = (RTGRouteSummary *)message;
+
+  if (response) {
+    NSString *str =[NSString stringWithFormat:
+                    @"%@\nFinished trip with %i points\nPassed %i features\n"
+                    "Travelled %i meters\nIt took %i seconds",
+                    self.outputLabel.text, response.pointCount, response.featureCount,
+                    response.distance, response.elapsedTime];
+    self.outputLabel.text = str;
+    NSLog(@"Finished trip with %i points", response.pointCount);
+    NSLog(@"Passed %i features", response.featureCount);
+    NSLog(@"Travelled %i meters", response.distance);
+    NSLog(@"It took %i seconds", response.elapsedTime);
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (error) {
+    NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
+    self.outputLabel.text = str;
+    NSLog(@"RPC error: %@", error);
+  }
 }
 
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+
+  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress callOptions:options];
 }
 
 - (void)viewDidAppear:(BOOL)animated {
@@ -250,7 +303,7 @@
  * Run the routeChat demo. Send some chat messages, and print any chat messages that are sent from
  * the server.
  */
-@interface RouteChatViewController : UIViewController
+@interface RouteChatViewController : UIViewController<GRPCProtoResponseHandler>
 
 @property (weak, nonatomic) IBOutlet UILabel *outputLabel;
 
@@ -260,38 +313,52 @@
   RTGRouteGuide *_service;
 }
 
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
+
 - (void)execRequest {
   NSArray *notes = @[[RTGRouteNote noteWithMessage:@"First message" latitude:0 longitude:0],
                      [RTGRouteNote noteWithMessage:@"Second message" latitude:0 longitude:1],
                      [RTGRouteNote noteWithMessage:@"Third message" latitude:1 longitude:0],
                      [RTGRouteNote noteWithMessage:@"Fourth message" latitude:0 longitude:0]];
-  GRXWriter *notesWriter = [[GRXWriter writerWithContainer:notes] map:^id(RTGRouteNote *note) {
-    NSLog(@"Sending message %@ at %@", note.message, note.location);
-    return note;
-  }];
 
-  [_service routeChatWithRequestsWriter:notesWriter
-                          eventHandler:^(BOOL done, RTGRouteNote *note, NSError *error) {
-    if (note) {
-      NSString *str =[NSString stringWithFormat:@"%@\nGot message %@ at %@",
-                      self.outputLabel.text, note.message, note.location];
-      self.outputLabel.text = str;
-      NSLog(@"Got message %@ at %@", note.message, note.location);
-    } else if (error) {
-      NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
-      self.outputLabel.text = str;
-      NSLog(@"RPC error: %@", error);
-    }
-    if (done) {
-      NSLog(@"Chat ended.");
-    }
-  }];
+  GRPCStreamingProtoCall *call = [_service routeChatWithResponseHandler:self
+                                                            callOptions:nil];
+  [call start];
+  for (RTGRouteNote *note in notes) {
+    [call writeMessage:note];
+  }
+  [call finish];
+}
+
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  RTGRouteNote *note = (RTGRouteNote *)message;
+  if (note) {
+    NSString *str =[NSString stringWithFormat:@"%@\nGot message %@ at %@",
+                    self.outputLabel.text, note.message, note.location];
+    self.outputLabel.text = str;
+    NSLog(@"Got message %@ at %@", note.message, note.location);
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (!error) {
+    NSLog(@"Chat ended.");
+  } else {
+    NSString *str =[NSString stringWithFormat:@"%@\nRPC error: %@", self.outputLabel.text, error];
+    self.outputLabel.text = str;
+    NSLog(@"RPC error: %@", error);
+  }
 }
 
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+
+  _service = [[RTGRouteGuide alloc] initWithHost:kHostAddress callOptions:options];
 }
 
 - (void)viewDidAppear:(BOOL)animated {
diff --git a/examples/python/errors/client.py b/examples/python/errors/client.py
index a79b8fc..c762d79 100644
--- a/examples/python/errors/client.py
+++ b/examples/python/errors/client.py
@@ -20,8 +20,8 @@
 from grpc_status import rpc_status
 from google.rpc import error_details_pb2
 
-from examples.protos import helloworld_pb2
-from examples.protos import helloworld_pb2_grpc
+from examples import helloworld_pb2
+from examples import helloworld_pb2_grpc
 
 _LOGGER = logging.getLogger(__name__)
 
diff --git a/examples/python/errors/server.py b/examples/python/errors/server.py
index f49586b..50d4a2a 100644
--- a/examples/python/errors/server.py
+++ b/examples/python/errors/server.py
@@ -24,8 +24,8 @@
 from google.protobuf import any_pb2
 from google.rpc import code_pb2, status_pb2, error_details_pb2
 
-from examples.protos import helloworld_pb2
-from examples.protos import helloworld_pb2_grpc
+from examples import helloworld_pb2
+from examples import helloworld_pb2_grpc
 
 _ONE_DAY_IN_SECONDS = 60 * 60 * 24
 
diff --git a/examples/python/errors/test/_error_handling_example_test.py b/examples/python/errors/test/_error_handling_example_test.py
index a79ca45..9eb81ba 100644
--- a/examples/python/errors/test/_error_handling_example_test.py
+++ b/examples/python/errors/test/_error_handling_example_test.py
@@ -26,7 +26,7 @@
 
 import grpc
 
-from examples.protos import helloworld_pb2_grpc
+from examples import helloworld_pb2_grpc
 from examples.python.errors import client as error_handling_client
 from examples.python.errors import server as error_handling_server
 
diff --git a/examples/python/multiprocessing/BUILD b/examples/python/multiprocessing/BUILD
index 6de1e94..0e135f4 100644
--- a/examples/python/multiprocessing/BUILD
+++ b/examples/python/multiprocessing/BUILD
@@ -15,12 +15,17 @@
 # limitations under the License.
 
 load("@grpc_python_dependencies//:requirements.bzl", "requirement")
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
+load("//bazel:python_rules.bzl", "py_proto_library")
+
+proto_library(
+    name = "prime_proto",
+    srcs = ["prime.proto"]
+)
 
 py_proto_library(
-    name = "prime_proto",
-    protos = ["prime.proto",],
-    deps = [requirement("protobuf")],
+    name = "prime_proto_pb2",
+    deps = [":prime_proto"],
+    well_known_protos = False,
 )
 
 py_binary(
@@ -29,7 +34,7 @@
     srcs = ["client.py"],
     deps = [
         "//src/python/grpcio/grpc:grpcio",
-        ":prime_proto",
+        ":prime_proto_pb2",
     ],
     default_python_version = "PY3",
 )
@@ -40,7 +45,7 @@
     srcs = ["server.py"],
     deps = [
         "//src/python/grpcio/grpc:grpcio",
-        ":prime_proto"
+        ":prime_proto_pb2"
     ] + select({
         "//conditions:default": [requirement("futures")],
         "//:python3": [],
diff --git a/examples/python/wait_for_ready/BUILD.bazel b/examples/python/wait_for_ready/BUILD.bazel
new file mode 100644
index 0000000..70daf83
--- /dev/null
+++ b/examples/python/wait_for_ready/BUILD.bazel
@@ -0,0 +1,32 @@
+# Copyright 2019 The 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.
+
+load("@grpc_python_dependencies//:requirements.bzl", "requirement")
+
+py_library(
+    name = "wait_for_ready_example",
+    testonly = 1,
+    srcs = ["wait_for_ready_example.py"],
+    deps = [
+        "//src/python/grpcio/grpc:grpcio",
+        "//examples:py_helloworld",
+    ],
+)
+
+py_test(
+    name = "test/_wait_for_ready_example_test",
+    srcs = ["test/_wait_for_ready_example_test.py"],
+    deps = [":wait_for_ready_example",],
+    size = "small",
+)
diff --git a/examples/python/wait_for_ready/README.md b/examples/python/wait_for_ready/README.md
new file mode 100644
index 0000000..6e873d2
--- /dev/null
+++ b/examples/python/wait_for_ready/README.md
@@ -0,0 +1,32 @@
+# gRPC Python Example for Wait-for-ready
+
+The default behavior of an RPC is to fail instantly if the server is not ready yet. This example demonstrates how to change that behavior.
+
+
+### Definition of 'wait-for-ready' semantics
+> If an RPC is issued but the channel is in TRANSIENT_FAILURE or SHUTDOWN states, the RPC is unable to be transmitted promptly. By default, gRPC implementations SHOULD fail such RPCs immediately. This is known as "fail fast," but the usage of the term is historical. RPCs SHOULD NOT fail as a result of the channel being in other states (CONNECTING, READY, or IDLE).
+> 
+> gRPC implementations MAY provide a per-RPC option to not fail RPCs as a result of the channel being in TRANSIENT_FAILURE state. Instead, the implementation queues the RPCs until the channel is READY. This is known as "wait for ready." The RPCs SHOULD still fail before READY if there are unrelated reasons, such as the channel is SHUTDOWN or the RPC's deadline is reached.
+> 
+> From https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md 
+
+
+### Use cases for 'wait-for-ready'
+
+When developers spin up gRPC clients and servers at the same time, it is very like to fail first couple RPC calls due to unavailability of the server. If developers failed to prepare for this situation, the result can be catastrophic. But with 'wait-for-ready' semantics, developers can initialize the client and server in any order, especially useful in testing.
+
+Also, developers may ensure the server is up before starting client. But in some cases like transient network failure may result in a temporary unavailability of the server. With 'wait-for-ready' semantics, those RPC calls will automatically wait until the server is ready to accept incoming requests.
+
+
+### DEMO Snippets
+
+```Python
+# Per RPC level
+stub = ...Stub(...)
+
+stub.important_transaction_1(..., wait_for_ready=True)
+stub.unimportant_transaction_2(...)
+stub.important_transaction_3(..., wait_for_ready=True)
+stub.unimportant_transaction_4(...)
+# The unimportant transactions can be status report, or health check, etc.
+```
diff --git a/examples/python/wait_for_ready/test/_wait_for_ready_example_test.py b/examples/python/wait_for_ready/test/_wait_for_ready_example_test.py
new file mode 100644
index 0000000..03e83a1
--- /dev/null
+++ b/examples/python/wait_for_ready/test/_wait_for_ready_example_test.py
@@ -0,0 +1,31 @@
+# Copyright 2019 The 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.
+"""Tests of the wait-for-ready example."""
+
+import unittest
+import logging
+
+from examples.python.wait_for_ready import wait_for_ready_example
+
+
+class WaitForReadyExampleTest(unittest.TestCase):
+
+    def test_wait_for_ready_example(self):
+        wait_for_ready_example.main()
+        # No unhandled exception raised, no deadlock, test passed!
+
+
+if __name__ == '__main__':
+    logging.basicConfig()
+    unittest.main(verbosity=2)
diff --git a/examples/python/wait_for_ready/wait_for_ready_example.py b/examples/python/wait_for_ready/wait_for_ready_example.py
new file mode 100644
index 0000000..a0f076e
--- /dev/null
+++ b/examples/python/wait_for_ready/wait_for_ready_example.py
@@ -0,0 +1,117 @@
+# Copyright 2019 The 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.
+"""The Python example of utilizing wait-for-ready flag."""
+
+from __future__ import print_function
+import logging
+from concurrent import futures
+from contextlib import contextmanager
+import socket
+import threading
+
+import grpc
+
+from examples import helloworld_pb2
+from examples import helloworld_pb2_grpc
+
+_LOGGER = logging.getLogger(__name__)
+_LOGGER.setLevel(logging.INFO)
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+
+
+@contextmanager
+def get_free_loopback_tcp_port():
+    if socket.has_ipv6:
+        tcp_socket = socket.socket(socket.AF_INET6)
+    else:
+        tcp_socket = socket.socket(socket.AF_INET)
+    tcp_socket.bind(('', 0))
+    address_tuple = tcp_socket.getsockname()
+    yield "localhost:%s" % (address_tuple[1])
+    tcp_socket.close()
+
+
+class Greeter(helloworld_pb2_grpc.GreeterServicer):
+
+    def SayHello(self, request, unused_context):
+        return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)
+
+
+def create_server(server_address):
+    server = grpc.server(futures.ThreadPoolExecutor())
+    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
+    bound_port = server.add_insecure_port(server_address)
+    assert bound_port == int(server_address.split(':')[-1])
+    return server
+
+
+def process(stub, wait_for_ready=None):
+    try:
+        response = stub.SayHello(
+            helloworld_pb2.HelloRequest(name='you'),
+            wait_for_ready=wait_for_ready)
+        message = response.message
+    except grpc.RpcError as rpc_error:
+        assert rpc_error.code() == grpc.StatusCode.UNAVAILABLE
+        assert not wait_for_ready
+        message = rpc_error
+    else:
+        assert wait_for_ready
+    _LOGGER.info("Wait-for-ready %s, client received: %s", "enabled"
+                 if wait_for_ready else "disabled", message)
+
+
+def main():
+    # Pick a random free port
+    with get_free_loopback_tcp_port() as server_address:
+
+        # Register connectivity event to notify main thread
+        transient_failure_event = threading.Event()
+
+        def wait_for_transient_failure(channel_connectivity):
+            if channel_connectivity == grpc.ChannelConnectivity.TRANSIENT_FAILURE:
+                transient_failure_event.set()
+
+        # Create gRPC channel
+        channel = grpc.insecure_channel(server_address)
+        channel.subscribe(wait_for_transient_failure)
+        stub = helloworld_pb2_grpc.GreeterStub(channel)
+
+        # Fire an RPC without wait_for_ready
+        thread_disabled_wait_for_ready = threading.Thread(
+            target=process, args=(stub, False))
+        thread_disabled_wait_for_ready.start()
+        # Fire an RPC with wait_for_ready
+        thread_enabled_wait_for_ready = threading.Thread(
+            target=process, args=(stub, True))
+        thread_enabled_wait_for_ready.start()
+
+    # Wait for the channel entering TRANSIENT FAILURE state.
+    transient_failure_event.wait()
+    server = create_server(server_address)
+    server.start()
+
+    # Expected to fail with StatusCode.UNAVAILABLE.
+    thread_disabled_wait_for_ready.join()
+    # Expected to success.
+    thread_enabled_wait_for_ready.join()
+
+    server.stop(None)
+    channel.close()
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+    main()
diff --git a/examples/ruby/without_protobuf/echo_client.rb b/examples/ruby/without_protobuf/echo_client.rb
index 79ee540..7932aab 100755
--- a/examples/ruby/without_protobuf/echo_client.rb
+++ b/examples/ruby/without_protobuf/echo_client.rb
@@ -28,7 +28,7 @@
   stub = EchoWithoutProtobuf::Stub.new('localhost:50051', :this_channel_is_insecure)
   user = ARGV.size > 0 ?  ARGV[0] : 'world'
   message = stub.echo("hello #{user}")
-  p "Reponse: #{message}"
+  p "Response: #{message}"
 end
 
 main
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index f031cbf..1dc2258 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -23,15 +23,15 @@
 Pod::Spec.new do |s|
   s.name     = 'gRPC-C++'
   # TODO (mxyan): use version that match gRPC version when pod is stabilized
-  # version = '1.20.1'
-  version = '0.0.8'
+  # version = '1.21.0-dev'
+  version = '0.0.8-dev'
   s.version  = version
   s.summary  = 'gRPC C++ library'
   s.homepage = 'https://grpc.io'
   s.license  = 'Apache License, Version 2.0'
   s.authors  = { 'The gRPC contributors' => 'grpc-packages@google.com' }
 
-  grpc_version = '1.20.1'
+  grpc_version = '1.21.0-dev'
 
   s.source = {
     :git => 'https://github.com/grpc/grpc.git',
@@ -82,16 +82,20 @@
     ss.source_files = 'include/grpcpp/alarm.h',
                       'include/grpcpp/alarm_impl.h',
                       'include/grpcpp/channel.h',
+                      'include/grpcpp/channel_impl.h',
                       'include/grpcpp/client_context.h',
                       'include/grpcpp/completion_queue.h',
                       'include/grpcpp/create_channel.h',
+                      'include/grpcpp/create_channel_impl.h',
                       'include/grpcpp/create_channel_posix.h',
                       'include/grpcpp/create_channel_posix_impl.h',
                       'include/grpcpp/ext/health_check_service_server_builder_option.h',
                       'include/grpcpp/generic/async_generic_service.h',
                       'include/grpcpp/generic/generic_stub.h',
+                      'include/grpcpp/generic/generic_stub_impl.h',
                       'include/grpcpp/grpcpp.h',
                       'include/grpcpp/health_check_service_interface.h',
+                      'include/grpcpp/health_check_service_interface_impl.h',
                       'include/grpcpp/impl/call.h',
                       'include/grpcpp/impl/channel_argument_option.h',
                       'include/grpcpp/impl/client_unary_call.h',
@@ -105,25 +109,34 @@
                       'include/grpcpp/impl/server_builder_option_impl.h',
                       'include/grpcpp/impl/server_builder_plugin.h',
                       'include/grpcpp/impl/server_initializer.h',
+                      'include/grpcpp/impl/server_initializer_impl.h',
                       'include/grpcpp/impl/service_type.h',
                       'include/grpcpp/resource_quota.h',
+                      'include/grpcpp/resource_quota_impl.h',
                       'include/grpcpp/security/auth_context.h',
                       'include/grpcpp/security/auth_metadata_processor.h',
+                      'include/grpcpp/security/auth_metadata_processor_impl.h',
                       'include/grpcpp/security/credentials.h',
+                      'include/grpcpp/security/credentials_impl.h',
                       'include/grpcpp/security/server_credentials.h',
+                      'include/grpcpp/security/server_credentials_impl.h',
                       'include/grpcpp/server.h',
                       'include/grpcpp/server_builder.h',
+                      'include/grpcpp/server_builder_impl.h',
                       'include/grpcpp/server_context.h',
+                      'include/grpcpp/server_impl.h',
                       'include/grpcpp/server_posix.h',
                       'include/grpcpp/server_posix_impl.h',
                       'include/grpcpp/support/async_stream.h',
                       'include/grpcpp/support/async_unary_call.h',
                       'include/grpcpp/support/byte_buffer.h',
                       'include/grpcpp/support/channel_arguments.h',
+                      'include/grpcpp/support/channel_arguments_impl.h',
                       'include/grpcpp/support/client_callback.h',
                       'include/grpcpp/support/client_interceptor.h',
                       'include/grpcpp/support/config.h',
                       'include/grpcpp/support/interceptor.h',
+                      'include/grpcpp/support/message_allocator.h',
                       'include/grpcpp/support/proto_buffer_reader.h',
                       'include/grpcpp/support/proto_buffer_writer.h',
                       'include/grpcpp/support/server_callback.h',
@@ -150,6 +163,7 @@
                       'include/grpcpp/impl/codegen/client_interceptor.h',
                       'include/grpcpp/impl/codegen/client_unary_call.h',
                       'include/grpcpp/impl/codegen/completion_queue.h',
+                      'include/grpcpp/impl/codegen/completion_queue_impl.h',
                       'include/grpcpp/impl/codegen/completion_queue_tag.h',
                       'include/grpcpp/impl/codegen/config.h',
                       'include/grpcpp/impl/codegen/core_codegen_interface.h',
@@ -158,6 +172,7 @@
                       'include/grpcpp/impl/codegen/intercepted_channel.h',
                       'include/grpcpp/impl/codegen/interceptor.h',
                       'include/grpcpp/impl/codegen/interceptor_common.h',
+                      'include/grpcpp/impl/codegen/message_allocator.h',
                       'include/grpcpp/impl/codegen/metadata_map.h',
                       'include/grpcpp/impl/codegen/method_handler_impl.h',
                       'include/grpcpp/impl/codegen/rpc_method.h',
@@ -175,7 +190,8 @@
                       'include/grpcpp/impl/codegen/string_ref.h',
                       'include/grpcpp/impl/codegen/stub_options.h',
                       'include/grpcpp/impl/codegen/sync_stream.h',
-                      'include/grpcpp/impl/codegen/time.h'
+                      'include/grpcpp/impl/codegen/time.h',
+                      'include/grpcpp/impl/codegen/sync.h'
   end
 
   s.subspec 'Implementation' do |ss|
@@ -253,11 +269,18 @@
                       'src/core/lib/gpr/tmpfile.h',
                       'src/core/lib/gpr/useful.h',
                       'src/core/lib/gprpp/abstract.h',
+                      'src/core/lib/gprpp/arena.h',
                       'src/core/lib/gprpp/atomic.h',
                       'src/core/lib/gprpp/fork.h',
+                      'src/core/lib/gprpp/global_config.h',
+                      'src/core/lib/gprpp/global_config_custom.h',
+                      'src/core/lib/gprpp/global_config_env.h',
+                      'src/core/lib/gprpp/global_config_generic.h',
                       'src/core/lib/gprpp/manual_constructor.h',
+                      'src/core/lib/gprpp/map.h',
                       'src/core/lib/gprpp/memory.h',
-                      'src/core/lib/gprpp/mutex_lock.h',
+                      'src/core/lib/gprpp/pair.h',
+                      'src/core/lib/gprpp/sync.h',
                       'src/core/lib/gprpp/thd.h',
                       'src/core/lib/profiling/timers.h',
                       'src/core/ext/transport/chttp2/transport/bin_decoder.h',
@@ -400,6 +423,7 @@
                       'src/core/lib/channel/handshaker_registry.h',
                       'src/core/lib/channel/status_util.h',
                       'src/core/lib/compression/algorithm_metadata.h',
+                      'src/core/lib/compression/compression_args.h',
                       'src/core/lib/compression/compression_internal.h',
                       'src/core/lib/compression/message_compress.h',
                       'src/core/lib/compression/stream_compression.h',
@@ -419,12 +443,15 @@
                       'src/core/lib/iomgr/block_annotate.h',
                       'src/core/lib/iomgr/buffer_list.h',
                       'src/core/lib/iomgr/call_combiner.h',
+                      'src/core/lib/iomgr/cfstream_handle.h',
                       'src/core/lib/iomgr/closure.h',
                       'src/core/lib/iomgr/combiner.h',
                       'src/core/lib/iomgr/dynamic_annotations.h',
                       'src/core/lib/iomgr/endpoint.h',
+                      'src/core/lib/iomgr/endpoint_cfstream.h',
                       'src/core/lib/iomgr/endpoint_pair.h',
                       'src/core/lib/iomgr/error.h',
+                      'src/core/lib/iomgr/error_cfstream.h',
                       'src/core/lib/iomgr/error_internal.h',
                       'src/core/lib/iomgr/ev_epoll1_linux.h',
                       'src/core/lib/iomgr/ev_epollex_linux.h',
@@ -535,6 +562,8 @@
                       'src/core/ext/filters/client_channel/lb_policy/subchannel_list.h',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h',
+                      'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h',
+                      'src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h',
                       'src/core/ext/filters/max_age/max_age_filter.h',
                       'src/core/ext/filters/message_size/message_size_filter.h',
                       'src/core/ext/filters/http/client_authority_filter.h',
@@ -568,11 +597,18 @@
                               'src/core/lib/gpr/tmpfile.h',
                               'src/core/lib/gpr/useful.h',
                               'src/core/lib/gprpp/abstract.h',
+                              'src/core/lib/gprpp/arena.h',
                               'src/core/lib/gprpp/atomic.h',
                               'src/core/lib/gprpp/fork.h',
+                              'src/core/lib/gprpp/global_config.h',
+                              'src/core/lib/gprpp/global_config_custom.h',
+                              'src/core/lib/gprpp/global_config_env.h',
+                              'src/core/lib/gprpp/global_config_generic.h',
                               'src/core/lib/gprpp/manual_constructor.h',
+                              'src/core/lib/gprpp/map.h',
                               'src/core/lib/gprpp/memory.h',
-                              'src/core/lib/gprpp/mutex_lock.h',
+                              'src/core/lib/gprpp/pair.h',
+                              'src/core/lib/gprpp/sync.h',
                               'src/core/lib/gprpp/thd.h',
                               'src/core/lib/profiling/timers.h',
                               'src/core/lib/avl/avl.h',
@@ -590,6 +626,7 @@
                               'src/core/lib/channel/handshaker_registry.h',
                               'src/core/lib/channel/status_util.h',
                               'src/core/lib/compression/algorithm_metadata.h',
+                              'src/core/lib/compression/compression_args.h',
                               'src/core/lib/compression/compression_internal.h',
                               'src/core/lib/compression/message_compress.h',
                               'src/core/lib/compression/stream_compression.h',
@@ -609,12 +646,15 @@
                               'src/core/lib/iomgr/block_annotate.h',
                               'src/core/lib/iomgr/buffer_list.h',
                               'src/core/lib/iomgr/call_combiner.h',
+                              'src/core/lib/iomgr/cfstream_handle.h',
                               'src/core/lib/iomgr/closure.h',
                               'src/core/lib/iomgr/combiner.h',
                               'src/core/lib/iomgr/dynamic_annotations.h',
                               'src/core/lib/iomgr/endpoint.h',
+                              'src/core/lib/iomgr/endpoint_cfstream.h',
                               'src/core/lib/iomgr/endpoint_pair.h',
                               'src/core/lib/iomgr/error.h',
+                              'src/core/lib/iomgr/error_cfstream.h',
                               'src/core/lib/iomgr/error_internal.h',
                               'src/core/lib/iomgr/ev_epoll1_linux.h',
                               'src/core/lib/iomgr/ev_epollex_linux.h',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 50fb324..cbf0a4b 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -22,7 +22,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-Core'
-  version = '1.20.1'
+  version = '1.21.0-dev'
   s.version  = version
   s.summary  = 'Core cross-platform gRPC library, written in C'
   s.homepage = 'https://grpc.io'
@@ -205,15 +205,21 @@
                       'src/core/lib/gpr/tmpfile.h',
                       'src/core/lib/gpr/useful.h',
                       'src/core/lib/gprpp/abstract.h',
+                      'src/core/lib/gprpp/arena.h',
                       'src/core/lib/gprpp/atomic.h',
                       'src/core/lib/gprpp/fork.h',
+                      'src/core/lib/gprpp/global_config.h',
+                      'src/core/lib/gprpp/global_config_custom.h',
+                      'src/core/lib/gprpp/global_config_env.h',
+                      'src/core/lib/gprpp/global_config_generic.h',
                       'src/core/lib/gprpp/manual_constructor.h',
+                      'src/core/lib/gprpp/map.h',
                       'src/core/lib/gprpp/memory.h',
-                      'src/core/lib/gprpp/mutex_lock.h',
+                      'src/core/lib/gprpp/pair.h',
+                      'src/core/lib/gprpp/sync.h',
                       'src/core/lib/gprpp/thd.h',
                       'src/core/lib/profiling/timers.h',
                       'src/core/lib/gpr/alloc.cc',
-                      'src/core/lib/gpr/arena.cc',
                       'src/core/lib/gpr/atm.cc',
                       'src/core/lib/gpr/cpu_iphone.cc',
                       'src/core/lib/gpr/cpu_linux.cc',
@@ -246,7 +252,9 @@
                       'src/core/lib/gpr/tmpfile_posix.cc',
                       'src/core/lib/gpr/tmpfile_windows.cc',
                       'src/core/lib/gpr/wrap_memcpy.cc',
+                      'src/core/lib/gprpp/arena.cc',
                       'src/core/lib/gprpp/fork.cc',
+                      'src/core/lib/gprpp/global_config_env.cc',
                       'src/core/lib/gprpp/thd_posix.cc',
                       'src/core/lib/gprpp/thd_windows.cc',
                       'src/core/lib/profiling/basic_timers.cc',
@@ -391,6 +399,7 @@
                       'src/core/lib/channel/handshaker_registry.h',
                       'src/core/lib/channel/status_util.h',
                       'src/core/lib/compression/algorithm_metadata.h',
+                      'src/core/lib/compression/compression_args.h',
                       'src/core/lib/compression/compression_internal.h',
                       'src/core/lib/compression/message_compress.h',
                       'src/core/lib/compression/stream_compression.h',
@@ -410,12 +419,15 @@
                       'src/core/lib/iomgr/block_annotate.h',
                       'src/core/lib/iomgr/buffer_list.h',
                       'src/core/lib/iomgr/call_combiner.h',
+                      'src/core/lib/iomgr/cfstream_handle.h',
                       'src/core/lib/iomgr/closure.h',
                       'src/core/lib/iomgr/combiner.h',
                       'src/core/lib/iomgr/dynamic_annotations.h',
                       'src/core/lib/iomgr/endpoint.h',
+                      'src/core/lib/iomgr/endpoint_cfstream.h',
                       'src/core/lib/iomgr/endpoint_pair.h',
                       'src/core/lib/iomgr/error.h',
+                      'src/core/lib/iomgr/error_cfstream.h',
                       'src/core/lib/iomgr/error_internal.h',
                       'src/core/lib/iomgr/ev_epoll1_linux.h',
                       'src/core/lib/iomgr/ev_epollex_linux.h',
@@ -526,6 +538,8 @@
                       'src/core/ext/filters/client_channel/lb_policy/subchannel_list.h',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h',
+                      'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h',
+                      'src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h',
                       'src/core/ext/filters/max_age/max_age_filter.h',
                       'src/core/ext/filters/message_size/message_size_filter.h',
                       'src/core/ext/filters/http/client_authority_filter.h',
@@ -545,6 +559,7 @@
                       'src/core/lib/channel/handshaker_registry.cc',
                       'src/core/lib/channel/status_util.cc',
                       'src/core/lib/compression/compression.cc',
+                      'src/core/lib/compression/compression_args.cc',
                       'src/core/lib/compression/compression_internal.cc',
                       'src/core/lib/compression/message_compress.cc',
                       'src/core/lib/compression/stream_compression.cc',
@@ -557,12 +572,15 @@
                       'src/core/lib/http/parser.cc',
                       'src/core/lib/iomgr/buffer_list.cc',
                       'src/core/lib/iomgr/call_combiner.cc',
+                      'src/core/lib/iomgr/cfstream_handle.cc',
                       'src/core/lib/iomgr/combiner.cc',
                       'src/core/lib/iomgr/endpoint.cc',
+                      'src/core/lib/iomgr/endpoint_cfstream.cc',
                       'src/core/lib/iomgr/endpoint_pair_posix.cc',
                       'src/core/lib/iomgr/endpoint_pair_uv.cc',
                       'src/core/lib/iomgr/endpoint_pair_windows.cc',
                       'src/core/lib/iomgr/error.cc',
+                      'src/core/lib/iomgr/error_cfstream.cc',
                       'src/core/lib/iomgr/ev_epoll1_linux.cc',
                       'src/core/lib/iomgr/ev_epollex_linux.cc',
                       'src/core/lib/iomgr/ev_poll_posix.cc',
@@ -583,6 +601,7 @@
                       'src/core/lib/iomgr/iomgr_custom.cc',
                       'src/core/lib/iomgr/iomgr_internal.cc',
                       'src/core/lib/iomgr/iomgr_posix.cc',
+                      'src/core/lib/iomgr/iomgr_posix_cfstream.cc',
                       'src/core/lib/iomgr/iomgr_uv.cc',
                       'src/core/lib/iomgr/iomgr_windows.cc',
                       'src/core/lib/iomgr/is_epollexclusive_available.cc',
@@ -611,6 +630,7 @@
                       'src/core/lib/iomgr/socket_utils_windows.cc',
                       'src/core/lib/iomgr/socket_windows.cc',
                       'src/core/lib/iomgr/tcp_client.cc',
+                      'src/core/lib/iomgr/tcp_client_cfstream.cc',
                       'src/core/lib/iomgr/tcp_client_custom.cc',
                       'src/core/lib/iomgr/tcp_client_posix.cc',
                       'src/core/lib/iomgr/tcp_client_windows.cc',
@@ -841,12 +861,16 @@
                       'src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc',
+                      'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc',
+                      'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc',
+                      'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc',
+                      'src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc',
                       'src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc',
                       'src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc',
                       'src/core/ext/filters/census/grpc_context.cc',
@@ -874,11 +898,18 @@
                               'src/core/lib/gpr/tmpfile.h',
                               'src/core/lib/gpr/useful.h',
                               'src/core/lib/gprpp/abstract.h',
+                              'src/core/lib/gprpp/arena.h',
                               'src/core/lib/gprpp/atomic.h',
                               'src/core/lib/gprpp/fork.h',
+                              'src/core/lib/gprpp/global_config.h',
+                              'src/core/lib/gprpp/global_config_custom.h',
+                              'src/core/lib/gprpp/global_config_env.h',
+                              'src/core/lib/gprpp/global_config_generic.h',
                               'src/core/lib/gprpp/manual_constructor.h',
+                              'src/core/lib/gprpp/map.h',
                               'src/core/lib/gprpp/memory.h',
-                              'src/core/lib/gprpp/mutex_lock.h',
+                              'src/core/lib/gprpp/pair.h',
+                              'src/core/lib/gprpp/sync.h',
                               'src/core/lib/gprpp/thd.h',
                               'src/core/lib/profiling/timers.h',
                               'src/core/ext/transport/chttp2/transport/bin_decoder.h',
@@ -1021,6 +1052,7 @@
                               'src/core/lib/channel/handshaker_registry.h',
                               'src/core/lib/channel/status_util.h',
                               'src/core/lib/compression/algorithm_metadata.h',
+                              'src/core/lib/compression/compression_args.h',
                               'src/core/lib/compression/compression_internal.h',
                               'src/core/lib/compression/message_compress.h',
                               'src/core/lib/compression/stream_compression.h',
@@ -1040,12 +1072,15 @@
                               'src/core/lib/iomgr/block_annotate.h',
                               'src/core/lib/iomgr/buffer_list.h',
                               'src/core/lib/iomgr/call_combiner.h',
+                              'src/core/lib/iomgr/cfstream_handle.h',
                               'src/core/lib/iomgr/closure.h',
                               'src/core/lib/iomgr/combiner.h',
                               'src/core/lib/iomgr/dynamic_annotations.h',
                               'src/core/lib/iomgr/endpoint.h',
+                              'src/core/lib/iomgr/endpoint_cfstream.h',
                               'src/core/lib/iomgr/endpoint_pair.h',
                               'src/core/lib/iomgr/error.h',
+                              'src/core/lib/iomgr/error_cfstream.h',
                               'src/core/lib/iomgr/error_internal.h',
                               'src/core/lib/iomgr/ev_epoll1_linux.h',
                               'src/core/lib/iomgr/ev_epollex_linux.h',
@@ -1156,6 +1191,8 @@
                               'src/core/ext/filters/client_channel/lb_policy/subchannel_list.h',
                               'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h',
                               'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h',
+                              'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h',
+                              'src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h',
                               'src/core/ext/filters/max_age/max_age_filter.h',
                               'src/core/ext/filters/message_size/message_size_filter.h',
                               'src/core/ext/filters/http/client_authority_filter.h',
@@ -1163,23 +1200,9 @@
                               'src/core/ext/filters/workarounds/workaround_utils.h'
   end
 
+  # CFStream is now default. Leaving this subspec only for compatibility purpose.
   s.subspec 'CFStream-Implementation' do |ss|
-    ss.header_mappings_dir = '.'
     ss.dependency "#{s.name}/Implementation", version
-    ss.pod_target_xcconfig = {
-      'GCC_PREPROCESSOR_DEFINITIONS' => 'GRPC_CFSTREAM=1'
-    }
-    ss.source_files = 'src/core/lib/iomgr/cfstream_handle.cc',
-                      'src/core/lib/iomgr/endpoint_cfstream.cc',
-                      'src/core/lib/iomgr/error_cfstream.cc',
-                      'src/core/lib/iomgr/iomgr_posix_cfstream.cc',
-                      'src/core/lib/iomgr/tcp_client_cfstream.cc',
-                      'src/core/lib/iomgr/cfstream_handle.h',
-                      'src/core/lib/iomgr/endpoint_cfstream.h',
-                      'src/core/lib/iomgr/error_cfstream.h'
-    ss.private_header_files = 'src/core/lib/iomgr/cfstream_handle.h',
-                              'src/core/lib/iomgr/endpoint_cfstream.h',
-                              'src/core/lib/iomgr/error_cfstream.h'
   end
 
   s.subspec 'Cronet-Interface' do |ss|
diff --git a/gRPC-ProtoRPC.podspec b/gRPC-ProtoRPC.podspec
index 89c367c..0e905bd 100644
--- a/gRPC-ProtoRPC.podspec
+++ b/gRPC-ProtoRPC.podspec
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-ProtoRPC'
-  version = '1.20.1'
+  version = '1.21.0-dev'
   s.version  = version
   s.summary  = 'RPC library for Protocol Buffers, based on gRPC'
   s.homepage = 'https://grpc.io'
@@ -53,12 +53,10 @@
 
     ss.source_files = "#{src_dir}/*.{h,m}"
   end
+
+  # CFStream is now default. Leaving this subspec only for compatibility purpose.
   s.subspec 'CFStream' do |ss|
-    ss.dependency 'gRPC/CFStream', version
     ss.dependency "#{s.name}/Main", version
-    ss.pod_target_xcconfig = {
-      'GCC_PREPROCESSOR_DEFINITIONS' => 'GRPC_CFSTREAM=1'
-    }
   end
 
   s.pod_target_xcconfig = {
diff --git a/gRPC-RxLibrary.podspec b/gRPC-RxLibrary.podspec
index 2dc3fe6..df86ef7 100644
--- a/gRPC-RxLibrary.podspec
+++ b/gRPC-RxLibrary.podspec
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-RxLibrary'
-  version = '1.20.1'
+  version = '1.21.0-dev'
   s.version  = version
   s.summary  = 'Reactive Extensions library for iOS/OSX.'
   s.homepage = 'https://grpc.io'
diff --git a/gRPC.podspec b/gRPC.podspec
index ec9cd77..b3c7a55 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -20,7 +20,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC'
-  version = '1.20.1'
+  version = '1.21.0-dev'
   s.version  = version
   s.summary  = 'gRPC client library for iOS/OSX'
   s.homepage = 'https://grpc.io'
@@ -64,14 +64,9 @@
     ss.dependency 'gRPC-Core', version
   end
 
-  # This subspec is mutually exclusive with the `Main` subspec
+  # CFStream is now default. Leaving this subspec only for compatibility purpose.
   s.subspec 'CFStream' do |ss|
-    ss.dependency 'gRPC-Core/CFStream-Implementation', version
     ss.dependency "#{s.name}/Main", version
-
-    ss.pod_target_xcconfig = {
-      'GCC_PREPROCESSOR_DEFINITIONS' => 'GRPC_CFSTREAM=1'
-    }
   end
 
   s.subspec 'GID' do |ss|
diff --git a/grpc.gemspec b/grpc.gemspec
index 52db5ec..6e229a5 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -30,7 +30,7 @@
   s.platform      = Gem::Platform::RUBY
 
   s.add_dependency 'google-protobuf', '~> 3.7'
-  s.add_dependency 'googleapis-common-protos-types', '~> 1.0.0'
+  s.add_dependency 'googleapis-common-protos-types', '~> 1.0'
 
   s.add_development_dependency 'bundler',            '~> 1.9'
   s.add_development_dependency 'facter',             '~> 2.4'
@@ -99,15 +99,21 @@
   s.files += %w( src/core/lib/gpr/tmpfile.h )
   s.files += %w( src/core/lib/gpr/useful.h )
   s.files += %w( src/core/lib/gprpp/abstract.h )
+  s.files += %w( src/core/lib/gprpp/arena.h )
   s.files += %w( src/core/lib/gprpp/atomic.h )
   s.files += %w( src/core/lib/gprpp/fork.h )
+  s.files += %w( src/core/lib/gprpp/global_config.h )
+  s.files += %w( src/core/lib/gprpp/global_config_custom.h )
+  s.files += %w( src/core/lib/gprpp/global_config_env.h )
+  s.files += %w( src/core/lib/gprpp/global_config_generic.h )
   s.files += %w( src/core/lib/gprpp/manual_constructor.h )
+  s.files += %w( src/core/lib/gprpp/map.h )
   s.files += %w( src/core/lib/gprpp/memory.h )
-  s.files += %w( src/core/lib/gprpp/mutex_lock.h )
+  s.files += %w( src/core/lib/gprpp/pair.h )
+  s.files += %w( src/core/lib/gprpp/sync.h )
   s.files += %w( src/core/lib/gprpp/thd.h )
   s.files += %w( src/core/lib/profiling/timers.h )
   s.files += %w( src/core/lib/gpr/alloc.cc )
-  s.files += %w( src/core/lib/gpr/arena.cc )
   s.files += %w( src/core/lib/gpr/atm.cc )
   s.files += %w( src/core/lib/gpr/cpu_iphone.cc )
   s.files += %w( src/core/lib/gpr/cpu_linux.cc )
@@ -140,7 +146,9 @@
   s.files += %w( src/core/lib/gpr/tmpfile_posix.cc )
   s.files += %w( src/core/lib/gpr/tmpfile_windows.cc )
   s.files += %w( src/core/lib/gpr/wrap_memcpy.cc )
+  s.files += %w( src/core/lib/gprpp/arena.cc )
   s.files += %w( src/core/lib/gprpp/fork.cc )
+  s.files += %w( src/core/lib/gprpp/global_config_env.cc )
   s.files += %w( src/core/lib/gprpp/thd_posix.cc )
   s.files += %w( src/core/lib/gprpp/thd_windows.cc )
   s.files += %w( src/core/lib/profiling/basic_timers.cc )
@@ -325,6 +333,7 @@
   s.files += %w( src/core/lib/channel/handshaker_registry.h )
   s.files += %w( src/core/lib/channel/status_util.h )
   s.files += %w( src/core/lib/compression/algorithm_metadata.h )
+  s.files += %w( src/core/lib/compression/compression_args.h )
   s.files += %w( src/core/lib/compression/compression_internal.h )
   s.files += %w( src/core/lib/compression/message_compress.h )
   s.files += %w( src/core/lib/compression/stream_compression.h )
@@ -344,12 +353,15 @@
   s.files += %w( src/core/lib/iomgr/block_annotate.h )
   s.files += %w( src/core/lib/iomgr/buffer_list.h )
   s.files += %w( src/core/lib/iomgr/call_combiner.h )
+  s.files += %w( src/core/lib/iomgr/cfstream_handle.h )
   s.files += %w( src/core/lib/iomgr/closure.h )
   s.files += %w( src/core/lib/iomgr/combiner.h )
   s.files += %w( src/core/lib/iomgr/dynamic_annotations.h )
   s.files += %w( src/core/lib/iomgr/endpoint.h )
+  s.files += %w( src/core/lib/iomgr/endpoint_cfstream.h )
   s.files += %w( src/core/lib/iomgr/endpoint_pair.h )
   s.files += %w( src/core/lib/iomgr/error.h )
+  s.files += %w( src/core/lib/iomgr/error_cfstream.h )
   s.files += %w( src/core/lib/iomgr/error_internal.h )
   s.files += %w( src/core/lib/iomgr/ev_epoll1_linux.h )
   s.files += %w( src/core/lib/iomgr/ev_epollex_linux.h )
@@ -460,6 +472,8 @@
   s.files += %w( src/core/ext/filters/client_channel/lb_policy/subchannel_list.h )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h )
+  s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h )
+  s.files += %w( src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h )
   s.files += %w( src/core/ext/filters/max_age/max_age_filter.h )
   s.files += %w( src/core/ext/filters/message_size/message_size_filter.h )
   s.files += %w( src/core/ext/filters/http/client_authority_filter.h )
@@ -479,6 +493,7 @@
   s.files += %w( src/core/lib/channel/handshaker_registry.cc )
   s.files += %w( src/core/lib/channel/status_util.cc )
   s.files += %w( src/core/lib/compression/compression.cc )
+  s.files += %w( src/core/lib/compression/compression_args.cc )
   s.files += %w( src/core/lib/compression/compression_internal.cc )
   s.files += %w( src/core/lib/compression/message_compress.cc )
   s.files += %w( src/core/lib/compression/stream_compression.cc )
@@ -491,12 +506,15 @@
   s.files += %w( src/core/lib/http/parser.cc )
   s.files += %w( src/core/lib/iomgr/buffer_list.cc )
   s.files += %w( src/core/lib/iomgr/call_combiner.cc )
+  s.files += %w( src/core/lib/iomgr/cfstream_handle.cc )
   s.files += %w( src/core/lib/iomgr/combiner.cc )
   s.files += %w( src/core/lib/iomgr/endpoint.cc )
+  s.files += %w( src/core/lib/iomgr/endpoint_cfstream.cc )
   s.files += %w( src/core/lib/iomgr/endpoint_pair_posix.cc )
   s.files += %w( src/core/lib/iomgr/endpoint_pair_uv.cc )
   s.files += %w( src/core/lib/iomgr/endpoint_pair_windows.cc )
   s.files += %w( src/core/lib/iomgr/error.cc )
+  s.files += %w( src/core/lib/iomgr/error_cfstream.cc )
   s.files += %w( src/core/lib/iomgr/ev_epoll1_linux.cc )
   s.files += %w( src/core/lib/iomgr/ev_epollex_linux.cc )
   s.files += %w( src/core/lib/iomgr/ev_poll_posix.cc )
@@ -517,6 +535,7 @@
   s.files += %w( src/core/lib/iomgr/iomgr_custom.cc )
   s.files += %w( src/core/lib/iomgr/iomgr_internal.cc )
   s.files += %w( src/core/lib/iomgr/iomgr_posix.cc )
+  s.files += %w( src/core/lib/iomgr/iomgr_posix_cfstream.cc )
   s.files += %w( src/core/lib/iomgr/iomgr_uv.cc )
   s.files += %w( src/core/lib/iomgr/iomgr_windows.cc )
   s.files += %w( src/core/lib/iomgr/is_epollexclusive_available.cc )
@@ -545,6 +564,7 @@
   s.files += %w( src/core/lib/iomgr/socket_utils_windows.cc )
   s.files += %w( src/core/lib/iomgr/socket_windows.cc )
   s.files += %w( src/core/lib/iomgr/tcp_client.cc )
+  s.files += %w( src/core/lib/iomgr/tcp_client_cfstream.cc )
   s.files += %w( src/core/lib/iomgr/tcp_client_custom.cc )
   s.files += %w( src/core/lib/iomgr/tcp_client_posix.cc )
   s.files += %w( src/core/lib/iomgr/tcp_client_windows.cc )
@@ -778,12 +798,16 @@
   s.files += %w( src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc )
+  s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc )
+  s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc )
+  s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc )
+  s.files += %w( src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc )
   s.files += %w( src/core/ext/filters/census/grpc_context.cc )
diff --git a/grpc.gyp b/grpc.gyp
index 322259d..833a0b4 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -218,7 +218,6 @@
       ],
       'sources': [
         'src/core/lib/gpr/alloc.cc',
-        'src/core/lib/gpr/arena.cc',
         'src/core/lib/gpr/atm.cc',
         'src/core/lib/gpr/cpu_iphone.cc',
         'src/core/lib/gpr/cpu_linux.cc',
@@ -251,7 +250,9 @@
         'src/core/lib/gpr/tmpfile_posix.cc',
         'src/core/lib/gpr/tmpfile_windows.cc',
         'src/core/lib/gpr/wrap_memcpy.cc',
+        'src/core/lib/gprpp/arena.cc',
         'src/core/lib/gprpp/fork.cc',
+        'src/core/lib/gprpp/global_config_env.cc',
         'src/core/lib/gprpp/thd_posix.cc',
         'src/core/lib/gprpp/thd_windows.cc',
         'src/core/lib/profiling/basic_timers.cc',
@@ -279,6 +280,7 @@
         'src/core/lib/channel/handshaker_registry.cc',
         'src/core/lib/channel/status_util.cc',
         'src/core/lib/compression/compression.cc',
+        'src/core/lib/compression/compression_args.cc',
         'src/core/lib/compression/compression_internal.cc',
         'src/core/lib/compression/message_compress.cc',
         'src/core/lib/compression/stream_compression.cc',
@@ -291,12 +293,15 @@
         'src/core/lib/http/parser.cc',
         'src/core/lib/iomgr/buffer_list.cc',
         'src/core/lib/iomgr/call_combiner.cc',
+        'src/core/lib/iomgr/cfstream_handle.cc',
         'src/core/lib/iomgr/combiner.cc',
         'src/core/lib/iomgr/endpoint.cc',
+        'src/core/lib/iomgr/endpoint_cfstream.cc',
         'src/core/lib/iomgr/endpoint_pair_posix.cc',
         'src/core/lib/iomgr/endpoint_pair_uv.cc',
         'src/core/lib/iomgr/endpoint_pair_windows.cc',
         'src/core/lib/iomgr/error.cc',
+        'src/core/lib/iomgr/error_cfstream.cc',
         'src/core/lib/iomgr/ev_epoll1_linux.cc',
         'src/core/lib/iomgr/ev_epollex_linux.cc',
         'src/core/lib/iomgr/ev_poll_posix.cc',
@@ -317,6 +322,7 @@
         'src/core/lib/iomgr/iomgr_custom.cc',
         'src/core/lib/iomgr/iomgr_internal.cc',
         'src/core/lib/iomgr/iomgr_posix.cc',
+        'src/core/lib/iomgr/iomgr_posix_cfstream.cc',
         'src/core/lib/iomgr/iomgr_uv.cc',
         'src/core/lib/iomgr/iomgr_windows.cc',
         'src/core/lib/iomgr/is_epollexclusive_available.cc',
@@ -345,6 +351,7 @@
         'src/core/lib/iomgr/socket_utils_windows.cc',
         'src/core/lib/iomgr/socket_windows.cc',
         'src/core/lib/iomgr/tcp_client.cc',
+        'src/core/lib/iomgr/tcp_client_cfstream.cc',
         'src/core/lib/iomgr/tcp_client_custom.cc',
         'src/core/lib/iomgr/tcp_client_posix.cc',
         'src/core/lib/iomgr/tcp_client_windows.cc',
@@ -578,12 +585,16 @@
         'src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc',
         'src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc',
         'src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc',
         'src/core/ext/filters/census/grpc_context.cc',
@@ -646,6 +657,7 @@
         'src/core/lib/channel/handshaker_registry.cc',
         'src/core/lib/channel/status_util.cc',
         'src/core/lib/compression/compression.cc',
+        'src/core/lib/compression/compression_args.cc',
         'src/core/lib/compression/compression_internal.cc',
         'src/core/lib/compression/message_compress.cc',
         'src/core/lib/compression/stream_compression.cc',
@@ -658,12 +670,15 @@
         'src/core/lib/http/parser.cc',
         'src/core/lib/iomgr/buffer_list.cc',
         'src/core/lib/iomgr/call_combiner.cc',
+        'src/core/lib/iomgr/cfstream_handle.cc',
         'src/core/lib/iomgr/combiner.cc',
         'src/core/lib/iomgr/endpoint.cc',
+        'src/core/lib/iomgr/endpoint_cfstream.cc',
         'src/core/lib/iomgr/endpoint_pair_posix.cc',
         'src/core/lib/iomgr/endpoint_pair_uv.cc',
         'src/core/lib/iomgr/endpoint_pair_windows.cc',
         'src/core/lib/iomgr/error.cc',
+        'src/core/lib/iomgr/error_cfstream.cc',
         'src/core/lib/iomgr/ev_epoll1_linux.cc',
         'src/core/lib/iomgr/ev_epollex_linux.cc',
         'src/core/lib/iomgr/ev_poll_posix.cc',
@@ -684,6 +699,7 @@
         'src/core/lib/iomgr/iomgr_custom.cc',
         'src/core/lib/iomgr/iomgr_internal.cc',
         'src/core/lib/iomgr/iomgr_posix.cc',
+        'src/core/lib/iomgr/iomgr_posix_cfstream.cc',
         'src/core/lib/iomgr/iomgr_uv.cc',
         'src/core/lib/iomgr/iomgr_windows.cc',
         'src/core/lib/iomgr/is_epollexclusive_available.cc',
@@ -712,6 +728,7 @@
         'src/core/lib/iomgr/socket_utils_windows.cc',
         'src/core/lib/iomgr/socket_windows.cc',
         'src/core/lib/iomgr/tcp_client.cc',
+        'src/core/lib/iomgr/tcp_client_cfstream.cc',
         'src/core/lib/iomgr/tcp_client_custom.cc',
         'src/core/lib/iomgr/tcp_client_posix.cc',
         'src/core/lib/iomgr/tcp_client_windows.cc',
@@ -890,6 +907,7 @@
         'src/core/lib/channel/handshaker_registry.cc',
         'src/core/lib/channel/status_util.cc',
         'src/core/lib/compression/compression.cc',
+        'src/core/lib/compression/compression_args.cc',
         'src/core/lib/compression/compression_internal.cc',
         'src/core/lib/compression/message_compress.cc',
         'src/core/lib/compression/stream_compression.cc',
@@ -902,12 +920,15 @@
         'src/core/lib/http/parser.cc',
         'src/core/lib/iomgr/buffer_list.cc',
         'src/core/lib/iomgr/call_combiner.cc',
+        'src/core/lib/iomgr/cfstream_handle.cc',
         'src/core/lib/iomgr/combiner.cc',
         'src/core/lib/iomgr/endpoint.cc',
+        'src/core/lib/iomgr/endpoint_cfstream.cc',
         'src/core/lib/iomgr/endpoint_pair_posix.cc',
         'src/core/lib/iomgr/endpoint_pair_uv.cc',
         'src/core/lib/iomgr/endpoint_pair_windows.cc',
         'src/core/lib/iomgr/error.cc',
+        'src/core/lib/iomgr/error_cfstream.cc',
         'src/core/lib/iomgr/ev_epoll1_linux.cc',
         'src/core/lib/iomgr/ev_epollex_linux.cc',
         'src/core/lib/iomgr/ev_poll_posix.cc',
@@ -928,6 +949,7 @@
         'src/core/lib/iomgr/iomgr_custom.cc',
         'src/core/lib/iomgr/iomgr_internal.cc',
         'src/core/lib/iomgr/iomgr_posix.cc',
+        'src/core/lib/iomgr/iomgr_posix_cfstream.cc',
         'src/core/lib/iomgr/iomgr_uv.cc',
         'src/core/lib/iomgr/iomgr_windows.cc',
         'src/core/lib/iomgr/is_epollexclusive_available.cc',
@@ -956,6 +978,7 @@
         'src/core/lib/iomgr/socket_utils_windows.cc',
         'src/core/lib/iomgr/socket_windows.cc',
         'src/core/lib/iomgr/tcp_client.cc',
+        'src/core/lib/iomgr/tcp_client_cfstream.cc',
         'src/core/lib/iomgr/tcp_client_custom.cc',
         'src/core/lib/iomgr/tcp_client_posix.cc',
         'src/core/lib/iomgr/tcp_client_windows.cc',
@@ -1110,6 +1133,7 @@
         'src/core/lib/channel/handshaker_registry.cc',
         'src/core/lib/channel/status_util.cc',
         'src/core/lib/compression/compression.cc',
+        'src/core/lib/compression/compression_args.cc',
         'src/core/lib/compression/compression_internal.cc',
         'src/core/lib/compression/message_compress.cc',
         'src/core/lib/compression/stream_compression.cc',
@@ -1122,12 +1146,15 @@
         'src/core/lib/http/parser.cc',
         'src/core/lib/iomgr/buffer_list.cc',
         'src/core/lib/iomgr/call_combiner.cc',
+        'src/core/lib/iomgr/cfstream_handle.cc',
         'src/core/lib/iomgr/combiner.cc',
         'src/core/lib/iomgr/endpoint.cc',
+        'src/core/lib/iomgr/endpoint_cfstream.cc',
         'src/core/lib/iomgr/endpoint_pair_posix.cc',
         'src/core/lib/iomgr/endpoint_pair_uv.cc',
         'src/core/lib/iomgr/endpoint_pair_windows.cc',
         'src/core/lib/iomgr/error.cc',
+        'src/core/lib/iomgr/error_cfstream.cc',
         'src/core/lib/iomgr/ev_epoll1_linux.cc',
         'src/core/lib/iomgr/ev_epollex_linux.cc',
         'src/core/lib/iomgr/ev_poll_posix.cc',
@@ -1148,6 +1175,7 @@
         'src/core/lib/iomgr/iomgr_custom.cc',
         'src/core/lib/iomgr/iomgr_internal.cc',
         'src/core/lib/iomgr/iomgr_posix.cc',
+        'src/core/lib/iomgr/iomgr_posix_cfstream.cc',
         'src/core/lib/iomgr/iomgr_uv.cc',
         'src/core/lib/iomgr/iomgr_windows.cc',
         'src/core/lib/iomgr/is_epollexclusive_available.cc',
@@ -1176,6 +1204,7 @@
         'src/core/lib/iomgr/socket_utils_windows.cc',
         'src/core/lib/iomgr/socket_windows.cc',
         'src/core/lib/iomgr/tcp_client.cc',
+        'src/core/lib/iomgr/tcp_client_cfstream.cc',
         'src/core/lib/iomgr/tcp_client_custom.cc',
         'src/core/lib/iomgr/tcp_client_posix.cc',
         'src/core/lib/iomgr/tcp_client_windows.cc',
@@ -1317,12 +1346,16 @@
         'src/core/ext/transport/inproc/inproc_transport.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc',
+        'src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc',
         'src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc',
         'src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc',
         'src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc',
@@ -1375,6 +1408,15 @@
       ],
     },
     {
+      'target_name': 'dns_test_util',
+      'type': 'static_library',
+      'dependencies': [
+      ],
+      'sources': [
+        'test/cpp/naming/dns_test_util.cc',
+      ],
+    },
+    {
       'target_name': 'grpc++',
       'type': 'static_library',
       'dependencies': [
diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h
index f1185d2..5318909 100644
--- a/include/grpc/grpc_security.h
+++ b/include/grpc/grpc_security.h
@@ -264,7 +264,7 @@
     const char* json_refresh_token, void* reserved);
 
 /** Creates an Oauth2 Access Token credentials with an access token that was
-   aquired by an out of band mechanism. */
+   acquired by an out of band mechanism. */
 GRPCAPI grpc_call_credentials* grpc_access_token_credentials_create(
     const char* access_token, void* reserved);
 
diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h
index 078db2b..65582e6 100644
--- a/include/grpc/impl/codegen/grpc_types.h
+++ b/include/grpc/impl/codegen/grpc_types.h
@@ -315,11 +315,11 @@
 #define GRPC_ARG_GRPCLB_CALL_TIMEOUT_MS "grpc.grpclb_call_timeout_ms"
 /* Timeout in milliseconds to wait for the serverlist from the grpclb load
    balancer before using fallback backend addresses from the resolver.
-   If 0, fallback will never be used. Default value is 10000. */
+   If 0, enter fallback mode immediately. Default value is 10000. */
 #define GRPC_ARG_GRPCLB_FALLBACK_TIMEOUT_MS "grpc.grpclb_fallback_timeout_ms"
 /* Timeout in milliseconds to wait for the serverlist from the xDS load
    balancer before using fallback backend addresses from the resolver.
-   If 0, fallback will never be used. Default value is 10000. */
+   If 0, enter fallback mode immediately. Default value is 10000. */
 #define GRPC_ARG_XDS_FALLBACK_TIMEOUT_MS "grpc.xds_fallback_timeout_ms"
 /** If non-zero, grpc server's cronet compression workaround will be enabled */
 #define GRPC_ARG_WORKAROUND_CRONET_COMPRESSION \
@@ -359,10 +359,12 @@
  * load balancing policy. Note that this only works with the "ares"
  * DNS resolver, and isn't supported by the "native" DNS resolver. */
 #define GRPC_ARG_DNS_ENABLE_SRV_QUERIES "grpc.dns_enable_srv_queries"
-/** If set, determines the number of milliseconds that the c-ares based
- * DNS resolver will wait on queries before cancelling them. The default value
- * is 10000. Setting this to "0" will disable c-ares query timeouts
- * entirely. */
+/** If set, determines an upper bound on the number of milliseconds that the
+ * c-ares based DNS resolver will wait on queries before cancelling them.
+ * The default value is 120,000. Setting this to "0" will disable the
+ * overall timeout entirely. Note that this doesn't include internal c-ares
+ * timeouts/backoff/retry logic, and so the actual DNS resolution may time out
+ * sooner than the value specified here. */
 #define GRPC_ARG_DNS_ARES_QUERY_TIMEOUT_MS "grpc.dns_ares_query_timeout"
 /** If set, uses a local subchannel pool within the channel. Otherwise, uses the
  * global subchannel pool. */
@@ -494,7 +496,8 @@
       field is guaranteed to be 0 */
   int success;
   /** The tag passed to grpc_call_start_batch etc to start this operation.
-      Only GRPC_OP_COMPLETE has a tag. */
+      *Only* GRPC_OP_COMPLETE has a tag. For all other grpc_completion_type
+      values, tag is uninitialized. */
   void* tag;
 } grpc_event;
 
diff --git a/include/grpc/impl/codegen/port_platform.h b/include/grpc/impl/codegen/port_platform.h
index b62bfcb..d7294d5 100644
--- a/include/grpc/impl/codegen/port_platform.h
+++ b/include/grpc/impl/codegen/port_platform.h
@@ -115,6 +115,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
@@ -144,6 +145,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #define GPR_SUPPORT_CHANNELS_FROM_FD 1
 #elif defined(__linux__)
@@ -170,6 +172,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
@@ -193,6 +196,7 @@
 #define GPR_PLATFORM_STRING "ios"
 #define GPR_CPU_IPHONE 1
 #define GPR_PTHREAD_TLS 1
+#define GRPC_CFSTREAM 1
 /* the c-ares resolver isnt safe to enable on iOS */
 #define GRPC_ARES 0
 #else /* TARGET_OS_IPHONE */
@@ -234,8 +238,8 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
-/* TODO(mxyan): Remove when CFStream becomes default */
 #ifndef GRPC_CFSTREAM
 #define GPR_SUPPORT_CHANNELS_FROM_FD 1
 #endif
@@ -260,6 +264,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #define GPR_SUPPORT_CHANNELS_FROM_FD 1
 #ifdef _LP64
@@ -283,6 +288,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #define GPR_SUPPORT_CHANNELS_FROM_FD 1
 #ifdef _LP64
@@ -303,6 +309,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
@@ -325,6 +332,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
@@ -353,6 +361,7 @@
 #define GPR_POSIX_SUBPROCESS 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
@@ -378,6 +387,7 @@
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_TIME 1
+#define GPR_HAS_PTHREAD_H 1
 #define GPR_GETPID_IN_UNISTD_H 1
 #else
 #error "Could not auto-detect platform"
diff --git a/include/grpc/impl/codegen/slice.h b/include/grpc/impl/codegen/slice.h
index 62339da..3567b1e 100644
--- a/include/grpc/impl/codegen/slice.h
+++ b/include/grpc/impl/codegen/slice.h
@@ -40,27 +40,6 @@
    reference ownership semantics (who should call unref?) and mutability
    constraints (is the callee allowed to modify the slice?) */
 
-typedef struct grpc_slice_refcount_vtable {
-  void (*ref)(void*);
-  void (*unref)(void*);
-  int (*eq)(grpc_slice a, grpc_slice b);
-  uint32_t (*hash)(grpc_slice slice);
-} grpc_slice_refcount_vtable;
-
-/** Reference count container for grpc_slice. Contains function pointers to
-   increment and decrement reference counts. Implementations should cleanup
-   when the reference count drops to zero.
-   Typically client code should not touch this, and use grpc_slice_malloc,
-   grpc_slice_new, or grpc_slice_new_with_len instead. */
-typedef struct grpc_slice_refcount {
-  const grpc_slice_refcount_vtable* vtable;
-  /** If a subset of this slice is taken, use this pointer for the refcount.
-     Typically points back to the refcount itself, however iterning
-     implementations can use this to avoid a verification step on each hash
-     or equality check */
-  struct grpc_slice_refcount* sub_refcount;
-} grpc_slice_refcount;
-
 /* Inlined half of grpc_slice is allowed to expand the size of the overall type
    by this many bytes */
 #define GRPC_SLICE_INLINE_EXTRA_SIZE sizeof(void*)
@@ -68,6 +47,7 @@
 #define GRPC_SLICE_INLINED_SIZE \
   (sizeof(size_t) + sizeof(uint8_t*) - 1 + GRPC_SLICE_INLINE_EXTRA_SIZE)
 
+struct grpc_slice_refcount;
 /** A grpc_slice s, if initialized, represents the byte range
    s.bytes[0..s.length-1].
 
diff --git a/include/grpc/impl/codegen/status.h b/include/grpc/impl/codegen/status.h
index 9bc3dc9..dec3b8f 100644
--- a/include/grpc/impl/codegen/status.h
+++ b/include/grpc/impl/codegen/status.h
@@ -128,7 +128,8 @@
 
   /** The service is currently unavailable.  This is a most likely a
      transient condition and may be corrected by retrying with
-     a backoff.
+     a backoff. Note that it is not always safe to retry non-idempotent
+     operations.
 
      WARNING: Although data MIGHT not have been transmitted when this
      status occurs, there is NOT A GUARANTEE that the server has not seen
diff --git a/include/grpc/slice.h b/include/grpc/slice.h
index ce48292..192a8cf 100644
--- a/include/grpc/slice.h
+++ b/include/grpc/slice.h
@@ -147,7 +147,7 @@
 GPRAPI int grpc_slice_rchr(grpc_slice s, char c);
 GPRAPI int grpc_slice_chr(grpc_slice s, char c);
 
-/** return the index of the first occurance of \a needle in \a haystack, or -1
+/** return the index of the first occurrence of \a needle in \a haystack, or -1
    if it's not found */
 GPRAPI int grpc_slice_slice(grpc_slice haystack, grpc_slice needle);
 
diff --git a/include/grpcpp/channel.h b/include/grpcpp/channel.h
index ee83396..163c804 100644
--- a/include/grpcpp/channel.h
+++ b/include/grpcpp/channel.h
@@ -19,20 +19,12 @@
 #ifndef GRPCPP_CHANNEL_H
 #define GRPCPP_CHANNEL_H
 
-#include <memory>
-#include <mutex>
-
-#include <grpc/grpc.h>
-#include <grpcpp/impl/call.h>
-#include <grpcpp/impl/codegen/channel_interface.h>
-#include <grpcpp/impl/codegen/client_interceptor.h>
-#include <grpcpp/impl/codegen/config.h>
-#include <grpcpp/impl/codegen/grpc_library.h>
-
-struct grpc_channel;
+#include <grpcpp/channel_impl.h>
 
 namespace grpc {
 
+typedef ::grpc_impl::Channel Channel;
+
 namespace experimental {
 /// Resets the channel's connection backoff.
 /// TODO(roth): Once we see whether this proves useful, either create a gRFC
@@ -40,75 +32,6 @@
 void ChannelResetConnectionBackoff(Channel* channel);
 }  // namespace experimental
 
-/// Channels represent a connection to an endpoint. Created by \a CreateChannel.
-class Channel final : public ChannelInterface,
-                      public internal::CallHook,
-                      public std::enable_shared_from_this<Channel>,
-                      private GrpcLibraryCodegen {
- public:
-  ~Channel();
-
-  /// Get the current channel state. If the channel is in IDLE and
-  /// \a try_to_connect is set to true, try to connect.
-  grpc_connectivity_state GetState(bool try_to_connect) override;
-
-  /// Returns the LB policy name, or the empty string if not yet available.
-  grpc::string GetLoadBalancingPolicyName() const;
-
-  /// Returns the service config in JSON form, or the empty string if
-  /// not available.
-  grpc::string GetServiceConfigJSON() const;
-
- private:
-  template <class InputMessage, class OutputMessage>
-  friend class internal::BlockingUnaryCallImpl;
-  friend void experimental::ChannelResetConnectionBackoff(Channel* channel);
-  friend std::shared_ptr<Channel> CreateChannelInternal(
-      const grpc::string& host, grpc_channel* c_channel,
-      std::vector<
-          std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-          interceptor_creators);
-  friend class internal::InterceptedChannel;
-  Channel(const grpc::string& host, grpc_channel* c_channel,
-          std::vector<
-              std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-              interceptor_creators);
-
-  internal::Call CreateCall(const internal::RpcMethod& method,
-                            ClientContext* context,
-                            CompletionQueue* cq) override;
-  void PerformOpsOnCall(internal::CallOpSetInterface* ops,
-                        internal::Call* call) override;
-  void* RegisterMethod(const char* method) override;
-
-  void NotifyOnStateChangeImpl(grpc_connectivity_state last_observed,
-                               gpr_timespec deadline, CompletionQueue* cq,
-                               void* tag) override;
-  bool WaitForStateChangeImpl(grpc_connectivity_state last_observed,
-                              gpr_timespec deadline) override;
-
-  CompletionQueue* CallbackCQ() override;
-
-  internal::Call CreateCallInternal(const internal::RpcMethod& method,
-                                    ClientContext* context, CompletionQueue* cq,
-                                    size_t interceptor_pos) override;
-
-  const grpc::string host_;
-  grpc_channel* const c_channel_;  // owned
-
-  // mu_ protects callback_cq_ (the per-channel callbackable completion queue)
-  std::mutex mu_;
-
-  // callback_cq_ references the callbackable completion queue associated
-  // with this channel (if any). It is set on the first call to CallbackCQ().
-  // It is _not owned_ by the channel; ownership belongs with its internal
-  // shutdown callback tag (invoked when the CQ is fully shutdown).
-  CompletionQueue* callback_cq_ = nullptr;
-
-  std::vector<std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-      interceptor_creators_;
-};
-
 }  // namespace grpc
 
 #endif  // GRPCPP_CHANNEL_H
diff --git a/include/grpcpp/channel_impl.h b/include/grpcpp/channel_impl.h
new file mode 100644
index 0000000..39917d2
--- /dev/null
+++ b/include/grpcpp/channel_impl.h
@@ -0,0 +1,125 @@
+/*
+ *
+ * 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 GRPCPP_CHANNEL_IMPL_H
+#define GRPCPP_CHANNEL_IMPL_H
+
+#include <memory>
+#include <mutex>
+
+#include <grpc/grpc.h>
+#include <grpcpp/impl/call.h>
+#include <grpcpp/impl/codegen/channel_interface.h>
+#include <grpcpp/impl/codegen/client_interceptor.h>
+#include <grpcpp/impl/codegen/completion_queue.h>
+#include <grpcpp/impl/codegen/config.h>
+#include <grpcpp/impl/codegen/grpc_library.h>
+#include <grpcpp/impl/codegen/sync.h>
+
+struct grpc_channel;
+
+namespace grpc {
+
+std::shared_ptr<::grpc_impl::Channel> CreateChannelInternal(
+    const grpc::string& host, grpc_channel* c_channel,
+    std::vector<
+        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+        interceptor_creators);
+}  // namespace grpc
+namespace grpc_impl {
+
+namespace experimental {
+/// Resets the channel's connection backoff.
+/// TODO(roth): Once we see whether this proves useful, either create a gRFC
+/// and change this to be a method of the Channel class, or remove it.
+void ChannelResetConnectionBackoff(Channel* channel);
+}  // namespace experimental
+
+/// Channels represent a connection to an endpoint. Created by \a CreateChannel.
+class Channel final : public ::grpc::ChannelInterface,
+                      public ::grpc::internal::CallHook,
+                      public std::enable_shared_from_this<Channel>,
+                      private ::grpc::GrpcLibraryCodegen {
+ public:
+  ~Channel();
+
+  /// Get the current channel state. If the channel is in IDLE and
+  /// \a try_to_connect is set to true, try to connect.
+  grpc_connectivity_state GetState(bool try_to_connect) override;
+
+  /// Returns the LB policy name, or the empty string if not yet available.
+  grpc::string GetLoadBalancingPolicyName() const;
+
+  /// Returns the service config in JSON form, or the empty string if
+  /// not available.
+  grpc::string GetServiceConfigJSON() const;
+
+ private:
+  template <class InputMessage, class OutputMessage>
+  friend class ::grpc::internal::BlockingUnaryCallImpl;
+  friend void experimental::ChannelResetConnectionBackoff(Channel* channel);
+  friend std::shared_ptr<Channel> grpc::CreateChannelInternal(
+      const grpc::string& host, grpc_channel* c_channel,
+      std::vector<std::unique_ptr<
+          ::grpc::experimental::ClientInterceptorFactoryInterface>>
+          interceptor_creators);
+  friend class ::grpc::internal::InterceptedChannel;
+  Channel(const grpc::string& host, grpc_channel* c_channel,
+          std::vector<std::unique_ptr<
+              ::grpc::experimental::ClientInterceptorFactoryInterface>>
+              interceptor_creators);
+
+  ::grpc::internal::Call CreateCall(const ::grpc::internal::RpcMethod& method,
+                                    ::grpc::ClientContext* context,
+                                    ::grpc::CompletionQueue* cq) override;
+  void PerformOpsOnCall(::grpc::internal::CallOpSetInterface* ops,
+                        ::grpc::internal::Call* call) override;
+  void* RegisterMethod(const char* method) override;
+
+  void NotifyOnStateChangeImpl(grpc_connectivity_state last_observed,
+                               gpr_timespec deadline,
+                               ::grpc::CompletionQueue* cq, void* tag) override;
+  bool WaitForStateChangeImpl(grpc_connectivity_state last_observed,
+                              gpr_timespec deadline) override;
+
+  ::grpc::CompletionQueue* CallbackCQ() override;
+
+  ::grpc::internal::Call CreateCallInternal(
+      const ::grpc::internal::RpcMethod& method, ::grpc::ClientContext* context,
+      ::grpc::CompletionQueue* cq, size_t interceptor_pos) override;
+
+  const grpc::string host_;
+  grpc_channel* const c_channel_;  // owned
+
+  // mu_ protects callback_cq_ (the per-channel callbackable completion queue)
+  grpc::internal::Mutex mu_;
+
+  // callback_cq_ references the callbackable completion queue associated
+  // with this channel (if any). It is set on the first call to CallbackCQ().
+  // It is _not owned_ by the channel; ownership belongs with its internal
+  // shutdown callback tag (invoked when the CQ is fully shutdown).
+  ::grpc::CompletionQueue* callback_cq_ = nullptr;
+
+  std::vector<
+      std::unique_ptr<::grpc::experimental::ClientInterceptorFactoryInterface>>
+      interceptor_creators_;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_CHANNEL_IMPL_H
diff --git a/include/grpcpp/create_channel.h b/include/grpcpp/create_channel.h
index e8a2a70..9b257ac 100644
--- a/include/grpcpp/create_channel.h
+++ b/include/grpcpp/create_channel.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,60 +19,38 @@
 #ifndef GRPCPP_CREATE_CHANNEL_H
 #define GRPCPP_CREATE_CHANNEL_H
 
-#include <memory>
-
-#include <grpcpp/channel.h>
-#include <grpcpp/impl/codegen/client_interceptor.h>
-#include <grpcpp/security/credentials.h>
+#include <grpcpp/create_channel_impl.h>
 #include <grpcpp/support/channel_arguments.h>
-#include <grpcpp/support/config.h>
 
 namespace grpc {
 
-/// Create a new \a Channel pointing to \a target.
-///
-/// \param target The URI of the endpoint to connect to.
-/// \param creds Credentials to use for the created channel. If it does not
-/// hold an object or is invalid, a lame channel (one on which all operations
-/// fail) is returned.
-std::shared_ptr<Channel> CreateChannel(
+static inline std::shared_ptr<::grpc::Channel> CreateChannel(
     const grpc::string& target,
-    const std::shared_ptr<ChannelCredentials>& creds);
+    const std::shared_ptr<ChannelCredentials>& creds) {
+  return ::grpc_impl::CreateChannelImpl(target, creds);
+}
 
-/// Create a new \em custom \a Channel pointing to \a target.
-///
-/// \warning For advanced use and testing ONLY. Override default channel
-/// arguments only if necessary.
-///
-/// \param target The URI of the endpoint to connect to.
-/// \param creds Credentials to use for the created channel. If it does not
-/// hold an object or is invalid, a lame channel (one on which all operations
-/// fail) is returned.
-/// \param args Options for channel creation.
-std::shared_ptr<Channel> CreateCustomChannel(
+static inline std::shared_ptr<::grpc::Channel> CreateCustomChannel(
     const grpc::string& target,
     const std::shared_ptr<ChannelCredentials>& creds,
-    const ChannelArguments& args);
+    const ChannelArguments& args) {
+  return ::grpc_impl::CreateCustomChannelImpl(target, creds, args);
+}
 
 namespace experimental {
-/// Create a new \em custom \a Channel pointing to \a target with \a
-/// interceptors being invoked per call.
-///
-/// \warning For advanced use and testing ONLY. Override default channel
-/// arguments only if necessary.
-///
-/// \param target The URI of the endpoint to connect to.
-/// \param creds Credentials to use for the created channel. If it does not
-/// hold an object or is invalid, a lame channel (one on which all operations
-/// fail) is returned.
-/// \param args Options for channel creation.
-std::shared_ptr<Channel> CreateCustomChannelWithInterceptors(
+
+static inline std::shared_ptr<::grpc::Channel>
+CreateCustomChannelWithInterceptors(
     const grpc::string& target,
     const std::shared_ptr<ChannelCredentials>& creds,
     const ChannelArguments& args,
     std::vector<
         std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-        interceptor_creators);
+        interceptor_creators) {
+  return ::grpc_impl::experimental::CreateCustomChannelWithInterceptors(
+      target, creds, args, std::move(interceptor_creators));
+}
+
 }  // namespace experimental
 }  // namespace grpc
 
diff --git a/include/grpcpp/create_channel_impl.h b/include/grpcpp/create_channel_impl.h
new file mode 100644
index 0000000..02896e6
--- /dev/null
+++ b/include/grpcpp/create_channel_impl.h
@@ -0,0 +1,78 @@
+/*
+ *
+ * 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 GRPCPP_CREATE_CHANNEL_IMPL_H
+#define GRPCPP_CREATE_CHANNEL_IMPL_H
+
+#include <memory>
+
+#include <grpcpp/channel.h>
+#include <grpcpp/impl/codegen/client_interceptor.h>
+#include <grpcpp/security/credentials.h>
+#include <grpcpp/support/channel_arguments.h>
+#include <grpcpp/support/config.h>
+
+namespace grpc_impl {
+/// Create a new \a Channel pointing to \a target.
+///
+/// \param target The URI of the endpoint to connect to.
+/// \param creds Credentials to use for the created channel. If it does not
+/// hold an object or is invalid, a lame channel (one on which all operations
+/// fail) is returned.
+std::shared_ptr<::grpc::Channel> CreateChannelImpl(
+    const grpc::string& target,
+    const std::shared_ptr<::grpc::ChannelCredentials>& creds);
+
+/// Create a new \em custom \a Channel pointing to \a target.
+///
+/// \warning For advanced use and testing ONLY. Override default channel
+/// arguments only if necessary.
+///
+/// \param target The URI of the endpoint to connect to.
+/// \param creds Credentials to use for the created channel. If it does not
+/// hold an object or is invalid, a lame channel (one on which all operations
+/// fail) is returned.
+/// \param args Options for channel creation.
+std::shared_ptr<::grpc::Channel> CreateCustomChannelImpl(
+    const grpc::string& target,
+    const std::shared_ptr<::grpc::ChannelCredentials>& creds,
+    const ::grpc::ChannelArguments& args);
+
+namespace experimental {
+/// Create a new \em custom \a Channel pointing to \a target with \a
+/// interceptors being invoked per call.
+///
+/// \warning For advanced use and testing ONLY. Override default channel
+/// arguments only if necessary.
+///
+/// \param target The URI of the endpoint to connect to.
+/// \param creds Credentials to use for the created channel. If it does not
+/// hold an object or is invalid, a lame channel (one on which all operations
+/// fail) is returned.
+/// \param args Options for channel creation.
+std::shared_ptr<::grpc::Channel> CreateCustomChannelWithInterceptors(
+    const grpc::string& target,
+    const std::shared_ptr<grpc::ChannelCredentials>& creds,
+    const ::grpc::ChannelArguments& args,
+    std::vector<
+        std::unique_ptr<grpc::experimental::ClientInterceptorFactoryInterface>>
+        interceptor_creators);
+}  // namespace experimental
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_CREATE_CHANNEL_IMPL_H
diff --git a/include/grpcpp/ext/proto_server_reflection_plugin.h b/include/grpcpp/ext/proto_server_reflection_plugin.h
index 1cfdc1b..f6f2202 100644
--- a/include/grpcpp/ext/proto_server_reflection_plugin.h
+++ b/include/grpcpp/ext/proto_server_reflection_plugin.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,34 +19,17 @@
 #ifndef GRPCPP_EXT_PROTO_SERVER_REFLECTION_PLUGIN_H
 #define GRPCPP_EXT_PROTO_SERVER_REFLECTION_PLUGIN_H
 
-#include <grpcpp/impl/server_builder_plugin.h>
-#include <grpcpp/support/config.h>
-
-namespace grpc {
-class ServerInitializer;
-class ProtoServerReflection;
-}  // namespace grpc
+#include <grpcpp/ext/proto_server_reflection_plugin_impl.h>
 
 namespace grpc {
 namespace reflection {
 
-class ProtoServerReflectionPlugin : public ::grpc::ServerBuilderPlugin {
- public:
-  ProtoServerReflectionPlugin();
-  ::grpc::string name() override;
-  void InitServer(::grpc::ServerInitializer* si) override;
-  void Finish(::grpc::ServerInitializer* si) override;
-  void ChangeArguments(const ::grpc::string& name, void* value) override;
-  bool has_async_methods() const override;
-  bool has_sync_methods() const override;
+typedef ::grpc_impl::reflection::ProtoServerReflectionPlugin
+    ProtoServerReflectionPlugin;
 
- private:
-  std::shared_ptr<grpc::ProtoServerReflection> reflection_service_;
-};
-
-/// Add proto reflection plugin to \a ServerBuilder.
-/// This function should be called at the static initialization time.
-void InitProtoReflectionServerBuilderPlugin();
+static inline void InitProtoReflectionServerBuilderPlugin() {
+  ::grpc_impl::reflection::InitProtoReflectionServerBuilderPlugin();
+}
 
 }  // namespace reflection
 }  // namespace grpc
diff --git a/include/grpcpp/ext/proto_server_reflection_plugin_impl.h b/include/grpcpp/ext/proto_server_reflection_plugin_impl.h
new file mode 100644
index 0000000..a06fe14
--- /dev/null
+++ b/include/grpcpp/ext/proto_server_reflection_plugin_impl.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * 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 GRPCPP_EXT_PROTO_SERVER_REFLECTION_PLUGIN_IMPL_H
+#define GRPCPP_EXT_PROTO_SERVER_REFLECTION_PLUGIN_IMPL_H
+
+#include <grpcpp/impl/server_builder_plugin.h>
+#include <grpcpp/support/config.h>
+
+namespace grpc {
+class ProtoServerReflection;
+}  // namespace grpc
+
+namespace grpc_impl {
+class ServerInitializer;
+
+namespace reflection {
+
+class ProtoServerReflectionPlugin : public ::grpc::ServerBuilderPlugin {
+ public:
+  ProtoServerReflectionPlugin();
+  ::grpc::string name() override;
+  void InitServer(::grpc_impl::ServerInitializer* si) override;
+  void Finish(::grpc_impl::ServerInitializer* si) override;
+  void ChangeArguments(const ::grpc::string& name, void* value) override;
+  bool has_async_methods() const override;
+  bool has_sync_methods() const override;
+
+ private:
+  std::shared_ptr<grpc::ProtoServerReflection> reflection_service_;
+};
+
+/// Add proto reflection plugin to \a ServerBuilder.
+/// This function should be called at the static initialization time.
+void InitProtoReflectionServerBuilderPlugin();
+
+}  // namespace reflection
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_EXT_PROTO_SERVER_REFLECTION_PLUGIN_IMPL_H
diff --git a/include/grpcpp/ext/server_load_reporting.h b/include/grpcpp/ext/server_load_reporting.h
index 939569c..0c39a9e 100644
--- a/include/grpcpp/ext/server_load_reporting.h
+++ b/include/grpcpp/ext/server_load_reporting.h
@@ -19,32 +19,22 @@
 #ifndef GRPCPP_EXT_SERVER_LOAD_REPORTING_H
 #define GRPCPP_EXT_SERVER_LOAD_REPORTING_H
 
-#include <grpc/support/port_platform.h>
-
-#include <grpc/load_reporting.h>
-#include <grpcpp/impl/codegen/config.h>
-#include <grpcpp/impl/codegen/server_context.h>
-#include <grpcpp/impl/server_builder_option.h>
+#include <grpcpp/ext/server_load_reporting_impl.h>
 
 namespace grpc {
 namespace load_reporter {
 namespace experimental {
 
-// The ServerBuilderOption to enable server-side load reporting feature. To
-// enable the feature, please make sure the binary builds with the
-// grpcpp_server_load_reporting library and set this option in the
-// ServerBuilder.
-class LoadReportingServiceServerBuilderOption : public ServerBuilderOption {
- public:
-  void UpdateArguments(::grpc::ChannelArguments* args) override;
-  void UpdatePlugins(std::vector<std::unique_ptr<::grpc::ServerBuilderPlugin>>*
-                         plugins) override;
-};
+typedef ::grpc_impl::load_reporter::experimental::
+    LoadReportingServiceServerBuilderOption
+        LoadReportingServiceServerBuilderOption;
 
-// Adds the load reporting cost with \a cost_name and \a cost_value in the
-// trailing metadata of the server context.
-void AddLoadReportingCost(grpc::ServerContext* ctx,
-                          const grpc::string& cost_name, double cost_value);
+static inline void AddLoadReportingCost(grpc::ServerContext* ctx,
+                                        const grpc::string& cost_name,
+                                        double cost_value) {
+  ::grpc_impl::load_reporter::experimental::AddLoadReportingCost(ctx, cost_name,
+                                                                 cost_value);
+}
 
 }  // namespace experimental
 }  // namespace load_reporter
diff --git a/include/grpcpp/ext/server_load_reporting_impl.h b/include/grpcpp/ext/server_load_reporting_impl.h
new file mode 100644
index 0000000..1b27e0a
--- /dev/null
+++ b/include/grpcpp/ext/server_load_reporting_impl.h
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPCPP_EXT_SERVER_LOAD_REPORTING_IMPL_H
+#define GRPCPP_EXT_SERVER_LOAD_REPORTING_IMPL_H
+
+#include <grpc/support/port_platform.h>
+
+#include <grpc/load_reporting.h>
+#include <grpcpp/impl/codegen/config.h>
+#include <grpcpp/impl/codegen/server_context.h>
+#include <grpcpp/impl/server_builder_option.h>
+
+namespace grpc_impl {
+namespace load_reporter {
+namespace experimental {
+
+// The ServerBuilderOption to enable server-side load reporting feature. To
+// enable the feature, please make sure the binary builds with the
+// grpcpp_server_load_reporting library and set this option in the
+// ServerBuilder.
+class LoadReportingServiceServerBuilderOption
+    : public grpc::ServerBuilderOption {
+ public:
+  void UpdateArguments(::grpc::ChannelArguments* args) override;
+  void UpdatePlugins(std::vector<std::unique_ptr<::grpc::ServerBuilderPlugin>>*
+                         plugins) override;
+};
+
+// Adds the load reporting cost with \a cost_name and \a cost_value in the
+// trailing metadata of the server context.
+void AddLoadReportingCost(grpc::ServerContext* ctx,
+                          const grpc::string& cost_name, double cost_value);
+
+}  // namespace experimental
+}  // namespace load_reporter
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_EXT_SERVER_LOAD_REPORTING_IMPL_H
diff --git a/include/grpcpp/generic/generic_stub.h b/include/grpcpp/generic/generic_stub.h
index 9252599..f4c4664 100644
--- a/include/grpcpp/generic/generic_stub.h
+++ b/include/grpcpp/generic/generic_stub.h
@@ -19,85 +19,11 @@
 #ifndef GRPCPP_GENERIC_GENERIC_STUB_H
 #define GRPCPP_GENERIC_GENERIC_STUB_H
 
-#include <functional>
-
-#include <grpcpp/support/async_stream.h>
-#include <grpcpp/support/async_unary_call.h>
-#include <grpcpp/support/byte_buffer.h>
-#include <grpcpp/support/client_callback.h>
-#include <grpcpp/support/status.h>
+#include <grpcpp/generic/generic_stub_impl.h>
 
 namespace grpc {
 
-class CompletionQueue;
-typedef ClientAsyncReaderWriter<ByteBuffer, ByteBuffer>
-    GenericClientAsyncReaderWriter;
-typedef ClientAsyncResponseReader<ByteBuffer> GenericClientAsyncResponseReader;
-
-/// Generic stubs provide a type-unsafe interface to call gRPC methods
-/// by name.
-class GenericStub final {
- public:
-  explicit GenericStub(std::shared_ptr<ChannelInterface> channel)
-      : channel_(channel) {}
-
-  /// Setup a call to a named method \a method using \a context, but don't
-  /// start it. Let it be started explicitly with StartCall and a tag.
-  /// The return value only indicates whether or not registration of the call
-  /// succeeded (i.e. the call won't proceed if the return value is nullptr).
-  std::unique_ptr<GenericClientAsyncReaderWriter> PrepareCall(
-      ClientContext* context, const grpc::string& method, CompletionQueue* cq);
-
-  /// Setup a unary call to a named method \a method using \a context, and don't
-  /// start it. Let it be started explicitly with StartCall.
-  /// The return value only indicates whether or not registration of the call
-  /// succeeded (i.e. the call won't proceed if the return value is nullptr).
-  std::unique_ptr<GenericClientAsyncResponseReader> PrepareUnaryCall(
-      ClientContext* context, const grpc::string& method,
-      const ByteBuffer& request, CompletionQueue* cq);
-
-  /// DEPRECATED for multi-threaded use
-  /// Begin a call to a named method \a method using \a context.
-  /// A tag \a tag will be delivered to \a cq when the call has been started
-  /// (i.e, initial metadata has been sent).
-  /// The return value only indicates whether or not registration of the call
-  /// succeeded (i.e. the call won't proceed if the return value is nullptr).
-  std::unique_ptr<GenericClientAsyncReaderWriter> Call(
-      ClientContext* context, const grpc::string& method, CompletionQueue* cq,
-      void* tag);
-
-  /// NOTE: class experimental_type is not part of the public API of this class
-  /// TODO(vjpai): Move these contents to the public API of GenericStub when
-  ///              they are no longer experimental
-  class experimental_type {
-   public:
-    explicit experimental_type(GenericStub* stub) : stub_(stub) {}
-
-    /// Setup and start a unary call to a named method \a method using
-    /// \a context and specifying the \a request and \a response buffers.
-    void UnaryCall(ClientContext* context, const grpc::string& method,
-                   const ByteBuffer* request, ByteBuffer* response,
-                   std::function<void(Status)> on_completion);
-
-    /// Setup a call to a named method \a method using \a context and tied to
-    /// \a reactor . Like any other bidi streaming RPC, it will not be activated
-    /// until StartCall is invoked on its reactor.
-    void PrepareBidiStreamingCall(
-        ClientContext* context, const grpc::string& method,
-        experimental::ClientBidiReactor<ByteBuffer, ByteBuffer>* reactor);
-
-   private:
-    GenericStub* stub_;
-  };
-
-  /// NOTE: The function experimental() is not stable public API. It is a view
-  /// to the experimental components of this class. It may be changed or removed
-  /// at any time.
-  experimental_type experimental() { return experimental_type(this); }
-
- private:
-  std::shared_ptr<ChannelInterface> channel_;
-};
+typedef ::grpc_impl::GenericStub GenericStub;
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/generic/generic_stub_impl.h b/include/grpcpp/generic/generic_stub_impl.h
new file mode 100644
index 0000000..9041461
--- /dev/null
+++ b/include/grpcpp/generic/generic_stub_impl.h
@@ -0,0 +1,114 @@
+/*
+ *
+ * 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 GRPCPP_GENERIC_GENERIC_STUB_IMPL_H
+#define GRPCPP_GENERIC_GENERIC_STUB_IMPL_H
+
+#include <functional>
+
+#include <grpcpp/support/async_stream.h>
+#include <grpcpp/support/async_unary_call.h>
+#include <grpcpp/support/byte_buffer.h>
+#include <grpcpp/support/client_callback.h>
+#include <grpcpp/support/status.h>
+
+namespace grpc {
+
+typedef ClientAsyncReaderWriter<ByteBuffer, ByteBuffer>
+    GenericClientAsyncReaderWriter;
+typedef ClientAsyncResponseReader<ByteBuffer> GenericClientAsyncResponseReader;
+}  // namespace grpc
+namespace grpc_impl {
+class CompletionQueue;
+
+/// Generic stubs provide a type-unsafe interface to call gRPC methods
+/// by name.
+class GenericStub final {
+ public:
+  explicit GenericStub(std::shared_ptr<grpc::ChannelInterface> channel)
+      : channel_(channel) {}
+
+  /// Setup a call to a named method \a method using \a context, but don't
+  /// start it. Let it be started explicitly with StartCall and a tag.
+  /// The return value only indicates whether or not registration of the call
+  /// succeeded (i.e. the call won't proceed if the return value is nullptr).
+  std::unique_ptr<grpc::GenericClientAsyncReaderWriter> PrepareCall(
+      grpc::ClientContext* context, const grpc::string& method,
+      grpc::CompletionQueue* cq);
+
+  /// Setup a unary call to a named method \a method using \a context, and don't
+  /// start it. Let it be started explicitly with StartCall.
+  /// The return value only indicates whether or not registration of the call
+  /// succeeded (i.e. the call won't proceed if the return value is nullptr).
+  std::unique_ptr<grpc::GenericClientAsyncResponseReader> PrepareUnaryCall(
+      grpc::ClientContext* context, const grpc::string& method,
+      const grpc::ByteBuffer& request, grpc::CompletionQueue* cq);
+
+  /// DEPRECATED for multi-threaded use
+  /// Begin a call to a named method \a method using \a context.
+  /// A tag \a tag will be delivered to \a cq when the call has been started
+  /// (i.e, initial metadata has been sent).
+  /// The return value only indicates whether or not registration of the call
+  /// succeeded (i.e. the call won't proceed if the return value is nullptr).
+  std::unique_ptr<grpc::GenericClientAsyncReaderWriter> Call(
+      grpc::ClientContext* context, const grpc::string& method,
+      grpc::CompletionQueue* cq, void* tag);
+
+  /// NOTE: class experimental_type is not part of the public API of this class
+  /// TODO(vjpai): Move these contents to the public API of GenericStub when
+  ///              they are no longer experimental
+  class experimental_type {
+   public:
+    explicit experimental_type(GenericStub* stub) : stub_(stub) {}
+
+    /// Setup and start a unary call to a named method \a method using
+    /// \a context and specifying the \a request and \a response buffers.
+    void UnaryCall(grpc::ClientContext* context, const grpc::string& method,
+                   const grpc::ByteBuffer* request, grpc::ByteBuffer* response,
+                   std::function<void(grpc::Status)> on_completion);
+
+    /// Setup and start a unary call to a named method \a method using
+    /// \a context and specifying the \a request and \a response buffers.
+    void UnaryCall(grpc::ClientContext* context, const grpc::string& method,
+                   const grpc::ByteBuffer* request, grpc::ByteBuffer* response,
+                   grpc::experimental::ClientUnaryReactor* reactor);
+
+    /// Setup a call to a named method \a method using \a context and tied to
+    /// \a reactor . Like any other bidi streaming RPC, it will not be activated
+    /// until StartCall is invoked on its reactor.
+    void PrepareBidiStreamingCall(
+        grpc::ClientContext* context, const grpc::string& method,
+        grpc::experimental::ClientBidiReactor<grpc::ByteBuffer,
+                                              grpc::ByteBuffer>* reactor);
+
+   private:
+    GenericStub* stub_;
+  };
+
+  /// NOTE: The function experimental() is not stable public API. It is a view
+  /// to the experimental components of this class. It may be changed or removed
+  /// at any time.
+  experimental_type experimental() { return experimental_type(this); }
+
+ private:
+  std::shared_ptr<grpc::ChannelInterface> channel_;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_GENERIC_GENERIC_STUB_IMPL_H
diff --git a/include/grpcpp/health_check_service_interface.h b/include/grpcpp/health_check_service_interface.h
index dfd4c39..a51b0d1 100644
--- a/include/grpcpp/health_check_service_interface.h
+++ b/include/grpcpp/health_check_service_interface.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2016 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,39 +19,22 @@
 #ifndef GRPCPP_HEALTH_CHECK_SERVICE_INTERFACE_H
 #define GRPCPP_HEALTH_CHECK_SERVICE_INTERFACE_H
 
-#include <grpcpp/support/config.h>
+#include <grpcpp/health_check_service_interface_impl.h>
 
 namespace grpc {
 
 const char kHealthCheckServiceInterfaceArg[] =
     "grpc.health_check_service_interface";
 
-/// The gRPC server uses this interface to expose the health checking service
-/// without depending on protobuf.
-class HealthCheckServiceInterface {
- public:
-  virtual ~HealthCheckServiceInterface() {}
+typedef ::grpc_impl::HealthCheckServiceInterface HealthCheckServiceInterface;
 
-  /// Set or change the serving status of the given \a service_name.
-  virtual void SetServingStatus(const grpc::string& service_name,
-                                bool serving) = 0;
-  /// Apply to all registered service names.
-  virtual void SetServingStatus(bool serving) = 0;
+static inline void EnableDefaultHealthCheckService(bool enable) {
+  ::grpc_impl::EnableDefaultHealthCheckService(enable);
+}
 
-  /// Set all registered service names to not serving and prevent future
-  /// state changes.
-  virtual void Shutdown() {}
-};
-
-/// Enable/disable the default health checking service. This applies to all C++
-/// servers created afterwards. For each server, user can override the default
-/// with a HealthCheckServiceServerBuilderOption.
-/// NOT thread safe.
-void EnableDefaultHealthCheckService(bool enable);
-
-/// Returns whether the default health checking service is enabled.
-/// NOT thread safe.
-bool DefaultHealthCheckServiceEnabled();
+static inline bool DefaultHealthCheckServiceEnabled() {
+  return ::grpc_impl::DefaultHealthCheckServiceEnabled();
+}
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/health_check_service_interface_impl.h b/include/grpcpp/health_check_service_interface_impl.h
new file mode 100644
index 0000000..025dadb
--- /dev/null
+++ b/include/grpcpp/health_check_service_interface_impl.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * Copyright 2016 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 GRPCPP_HEALTH_CHECK_SERVICE_INTERFACE_IMPL_H
+#define GRPCPP_HEALTH_CHECK_SERVICE_INTERFACE_IMPL_H
+
+#include <grpcpp/support/config.h>
+
+namespace grpc_impl {
+
+/// The gRPC server uses this interface to expose the health checking service
+/// without depending on protobuf.
+class HealthCheckServiceInterface {
+ public:
+  virtual ~HealthCheckServiceInterface() {}
+
+  /// Set or change the serving status of the given \a service_name.
+  virtual void SetServingStatus(const grpc::string& service_name,
+                                bool serving) = 0;
+  /// Apply to all registered service names.
+  virtual void SetServingStatus(bool serving) = 0;
+
+  /// Set all registered service names to not serving and prevent future
+  /// state changes.
+  virtual void Shutdown() {}
+};
+
+/// Enable/disable the default health checking service. This applies to all C++
+/// servers created afterwards. For each server, user can override the default
+/// with a HealthCheckServiceServerBuilderOption.
+/// NOT thread safe.
+void EnableDefaultHealthCheckService(bool enable);
+
+/// Returns whether the default health checking service is enabled.
+/// NOT thread safe.
+bool DefaultHealthCheckServiceEnabled();
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_HEALTH_CHECK_SERVICE_INTERFACE_IMPL_H
diff --git a/include/grpcpp/impl/codegen/async_generic_service.h b/include/grpcpp/impl/codegen/async_generic_service.h
index 759f668..46d0912 100644
--- a/include/grpcpp/impl/codegen/async_generic_service.h
+++ b/include/grpcpp/impl/codegen/async_generic_service.h
@@ -39,7 +39,7 @@
   const grpc::string& host() const { return host_; }
 
  private:
-  friend class Server;
+  friend class grpc_impl::Server;
   friend class ServerInterface;
 
   void Clear() {
@@ -79,8 +79,8 @@
                    ServerCompletionQueue* notification_cq, void* tag);
 
  private:
-  friend class Server;
-  Server* server_;
+  friend class grpc_impl::Server;
+  grpc_impl::Server* server_;
 };
 
 namespace experimental {
@@ -135,14 +135,14 @@
   }
 
  private:
-  friend class ::grpc::Server;
+  friend class ::grpc_impl::Server;
 
   internal::CallbackBidiHandler<ByteBuffer, ByteBuffer>* Handler() {
     return new internal::CallbackBidiHandler<ByteBuffer, ByteBuffer>(
         [this] { return CreateReactor(); });
   }
 
-  Server* server_{nullptr};
+  grpc_impl::Server* server_{nullptr};
 };
 }  // namespace experimental
 }  // namespace grpc
diff --git a/include/grpcpp/impl/codegen/async_stream.h b/include/grpcpp/impl/codegen/async_stream.h
index bfb2df4..f957726 100644
--- a/include/grpcpp/impl/codegen/async_stream.h
+++ b/include/grpcpp/impl/codegen/async_stream.h
@@ -28,8 +28,6 @@
 
 namespace grpc {
 
-class CompletionQueue;
-
 namespace internal {
 /// Common interface for all client side asynchronous streaming.
 class ClientAsyncStreamingInterface {
@@ -661,7 +659,7 @@
   /// some failure occurred when trying to do so.
   ///
   /// gRPC doesn't take ownership or a reference to \a msg or \a status, so it
-  /// is safe to to deallocate once Finish returns.
+  /// is safe to deallocate once Finish returns.
   ///
   /// \param[in] tag Tag identifying this request.
   /// \param[in] status To be sent to the client as the result of this call.
@@ -735,7 +733,7 @@
   /// Note: \a msg is not sent if \a status has a non-OK code.
   ///
   /// gRPC doesn't take ownership or a reference to \a msg and \a status, so it
-  /// is safe to to deallocate once Finish returns.
+  /// is safe to deallocate once Finish returns.
   void Finish(const W& msg, const Status& status, void* tag) override {
     finish_ops_.set_output_tag(tag);
     if (!ctx_->sent_initial_metadata_) {
@@ -830,7 +828,7 @@
   /// in a single step.
   ///
   /// gRPC doesn't take ownership or a reference to \a msg and \a status, so it
-  /// is safe to to deallocate once WriteAndFinish returns.
+  /// is safe to deallocate once WriteAndFinish returns.
   ///
   /// \param[in] msg The message to be written.
   /// \param[in] options The WriteOptions to be used to write this message.
@@ -897,7 +895,7 @@
   /// Note: \a status must have an OK code.
   ///
   /// gRPC doesn't take ownership or a reference to \a msg and \a status, so it
-  /// is safe to to deallocate once WriteAndFinish returns.
+  /// is safe to deallocate once WriteAndFinish returns.
   void WriteAndFinish(const W& msg, WriteOptions options, const Status& status,
                       void* tag) override {
     write_ops_.set_output_tag(tag);
@@ -993,7 +991,7 @@
   /// single step.
   ///
   /// gRPC doesn't take ownership or a reference to \a msg and \a status, so it
-  /// is safe to to deallocate once WriteAndFinish returns.
+  /// is safe to deallocate once WriteAndFinish returns.
   ///
   /// \param[in] msg The message to be written.
   /// \param[in] options The WriteOptions to be used to write this message.
@@ -1068,7 +1066,7 @@
   /// Note: \a status must have an OK code.
   //
   /// gRPC doesn't take ownership or a reference to \a msg and \a status, so it
-  /// is safe to to deallocate once WriteAndFinish returns.
+  /// is safe to deallocate once WriteAndFinish returns.
   void WriteAndFinish(const W& msg, WriteOptions options, const Status& status,
                       void* tag) override {
     write_ops_.set_output_tag(tag);
@@ -1099,7 +1097,7 @@
   }
 
  private:
-  friend class ::grpc::Server;
+  friend class ::grpc_impl::Server;
 
   void BindCall(::grpc::internal::Call* call) override { call_ = *call; }
 
diff --git a/include/grpcpp/impl/codegen/async_unary_call.h b/include/grpcpp/impl/codegen/async_unary_call.h
index 89dcb12..4b97cf2 100644
--- a/include/grpcpp/impl/codegen/async_unary_call.h
+++ b/include/grpcpp/impl/codegen/async_unary_call.h
@@ -29,7 +29,6 @@
 
 namespace grpc {
 
-class CompletionQueue;
 extern CoreCodegenInterface* g_core_codegen_interface;
 
 /// An interface relevant for async client side unary RPCs (which send
diff --git a/include/grpcpp/impl/codegen/call.h b/include/grpcpp/impl/codegen/call.h
index c040c30..eefa4a7 100644
--- a/include/grpcpp/impl/codegen/call.h
+++ b/include/grpcpp/impl/codegen/call.h
@@ -21,9 +21,11 @@
 #include <grpc/impl/codegen/grpc_types.h>
 #include <grpcpp/impl/codegen/call_hook.h>
 
-namespace grpc {
+namespace grpc_impl {
 class CompletionQueue;
+}
 
+namespace grpc {
 namespace experimental {
 class ClientRpcInfo;
 class ServerRpcInfo;
@@ -41,13 +43,13 @@
         call_(nullptr),
         max_receive_message_size_(-1) {}
   /** call is owned by the caller */
-  Call(grpc_call* call, CallHook* call_hook, CompletionQueue* cq)
+  Call(grpc_call* call, CallHook* call_hook, ::grpc_impl::CompletionQueue* cq)
       : call_hook_(call_hook),
         cq_(cq),
         call_(call),
         max_receive_message_size_(-1) {}
 
-  Call(grpc_call* call, CallHook* call_hook, CompletionQueue* cq,
+  Call(grpc_call* call, CallHook* call_hook, ::grpc_impl::CompletionQueue* cq,
        experimental::ClientRpcInfo* rpc_info)
       : call_hook_(call_hook),
         cq_(cq),
@@ -55,7 +57,7 @@
         max_receive_message_size_(-1),
         client_rpc_info_(rpc_info) {}
 
-  Call(grpc_call* call, CallHook* call_hook, CompletionQueue* cq,
+  Call(grpc_call* call, CallHook* call_hook, ::grpc_impl::CompletionQueue* cq,
        int max_receive_message_size, experimental::ServerRpcInfo* rpc_info)
       : call_hook_(call_hook),
         cq_(cq),
@@ -68,7 +70,7 @@
   }
 
   grpc_call* call() const { return call_; }
-  CompletionQueue* cq() const { return cq_; }
+  ::grpc_impl::CompletionQueue* cq() const { return cq_; }
 
   int max_receive_message_size() const { return max_receive_message_size_; }
 
@@ -82,7 +84,7 @@
 
  private:
   CallHook* call_hook_;
-  CompletionQueue* cq_;
+  ::grpc_impl::CompletionQueue* cq_;
   grpc_call* call_;
   int max_receive_message_size_;
   experimental::ClientRpcInfo* client_rpc_info_ = nullptr;
diff --git a/include/grpcpp/impl/codegen/call_op_set.h b/include/grpcpp/impl/codegen/call_op_set.h
index 4ca87a9..d810625 100644
--- a/include/grpcpp/impl/codegen/call_op_set.h
+++ b/include/grpcpp/impl/codegen/call_op_set.h
@@ -48,7 +48,6 @@
 
 namespace grpc {
 
-class CompletionQueue;
 extern CoreCodegenInterface* g_core_codegen_interface;
 
 namespace internal {
diff --git a/include/grpcpp/impl/codegen/channel_interface.h b/include/grpcpp/impl/codegen/channel_interface.h
index 5353f5f..9df233b 100644
--- a/include/grpcpp/impl/codegen/channel_interface.h
+++ b/include/grpcpp/impl/codegen/channel_interface.h
@@ -24,10 +24,13 @@
 #include <grpcpp/impl/codegen/status.h>
 #include <grpcpp/impl/codegen/time.h>
 
+namespace grpc_impl {
+class CompletionQueue;
+}
+
 namespace grpc {
 class ChannelInterface;
 class ClientContext;
-class CompletionQueue;
 
 template <class R>
 class ClientReader;
@@ -58,6 +61,7 @@
 class ClientCallbackReaderFactory;
 template <class W>
 class ClientCallbackWriterFactory;
+class ClientCallbackUnaryFactory;
 class InterceptedChannel;
 }  // namespace internal
 
@@ -73,7 +77,7 @@
   /// deadline expires. \a GetState needs to called to get the current state.
   template <typename T>
   void NotifyOnStateChange(grpc_connectivity_state last_observed, T deadline,
-                           CompletionQueue* cq, void* tag) {
+                           ::grpc_impl::CompletionQueue* cq, void* tag) {
     TimePoint<T> deadline_tp(deadline);
     NotifyOnStateChangeImpl(last_observed, deadline_tp.raw_time(), cq, tag);
   }
@@ -117,6 +121,7 @@
   friend class ::grpc::internal::ClientCallbackReaderFactory;
   template <class W>
   friend class ::grpc::internal::ClientCallbackWriterFactory;
+  friend class ::grpc::internal::ClientCallbackUnaryFactory;
   template <class InputMessage, class OutputMessage>
   friend class ::grpc::internal::BlockingUnaryCallImpl;
   template <class InputMessage, class OutputMessage>
@@ -125,13 +130,14 @@
   friend class ::grpc::internal::InterceptedChannel;
   virtual internal::Call CreateCall(const internal::RpcMethod& method,
                                     ClientContext* context,
-                                    CompletionQueue* cq) = 0;
+                                    ::grpc_impl::CompletionQueue* cq) = 0;
   virtual void PerformOpsOnCall(internal::CallOpSetInterface* ops,
                                 internal::Call* call) = 0;
   virtual void* RegisterMethod(const char* method) = 0;
   virtual void NotifyOnStateChangeImpl(grpc_connectivity_state last_observed,
                                        gpr_timespec deadline,
-                                       CompletionQueue* cq, void* tag) = 0;
+                                       ::grpc_impl::CompletionQueue* cq,
+                                       void* tag) = 0;
   virtual bool WaitForStateChangeImpl(grpc_connectivity_state last_observed,
                                       gpr_timespec deadline) = 0;
 
@@ -144,7 +150,7 @@
   // change (even though this is private and non-API)
   virtual internal::Call CreateCallInternal(const internal::RpcMethod& method,
                                             ClientContext* context,
-                                            CompletionQueue* cq,
+                                            ::grpc_impl::CompletionQueue* cq,
                                             size_t interceptor_pos) {
     return internal::Call();
   }
@@ -157,7 +163,7 @@
   // Returns nullptr (rather than being pure) since this is a post-1.0 method
   // and adding a new pure method to an interface would be a breaking change
   // (even though this is private and non-API)
-  virtual CompletionQueue* CallbackCQ() { return nullptr; }
+  virtual ::grpc_impl::CompletionQueue* CallbackCQ() { return nullptr; }
 };
 }  // namespace grpc
 
diff --git a/include/grpcpp/impl/codegen/client_callback.h b/include/grpcpp/impl/codegen/client_callback.h
index 53c57b5..dda9aec 100644
--- a/include/grpcpp/impl/codegen/client_callback.h
+++ b/include/grpcpp/impl/codegen/client_callback.h
@@ -29,11 +29,13 @@
 #include <grpcpp/impl/codegen/core_codegen_interface.h>
 #include <grpcpp/impl/codegen/status.h>
 
+namespace grpc_impl {
+class Channel;
+}
+
 namespace grpc {
 
-class Channel;
 class ClientContext;
-class CompletionQueue;
 
 namespace internal {
 class RpcMethod;
@@ -100,6 +102,7 @@
 class ClientReadReactor;
 template <class Request>
 class ClientWriteReactor;
+class ClientUnaryReactor;
 
 // NOTE: The streaming objects are not actually implemented in the public API.
 //       These interfaces are provided for mocking only. Typical applications
@@ -157,6 +160,15 @@
   }
 };
 
+class ClientCallbackUnary {
+ public:
+  virtual ~ClientCallbackUnary() {}
+  virtual void StartCall() = 0;
+
+ protected:
+  void BindReactor(ClientUnaryReactor* reactor);
+};
+
 // The following classes are the reactor interfaces that are to be implemented
 // by the user. They are passed in to the library as an argument to a call on a
 // stub (either a codegen-ed call or a generic call). The streaming RPC is
@@ -164,6 +176,8 @@
 // StartWrite, or AddHold operations on the streaming object. Note that none of
 // the classes are pure; all reactions have a default empty reaction so that the
 // user class only needs to override those classes that it cares about.
+// The reactor must be passed to the stub invocation before any of the below
+// operations can be called.
 
 /// \a ClientBidiReactor is the interface for a bidirectional streaming RPC.
 template <class Request, class Response>
@@ -346,6 +360,36 @@
   ClientCallbackWriter<Request>* writer_;
 };
 
+/// \a ClientUnaryReactor is a reactor-style interface for a unary RPC.
+/// This is _not_ a common way of invoking a unary RPC. In practice, this
+/// option should be used only if the unary RPC wants to receive initial
+/// metadata without waiting for the response to complete. Most deployments of
+/// RPC systems do not use this option, but it is needed for generality.
+/// All public methods behave as in ClientBidiReactor.
+/// StartCall is included for consistency with the other reactor flavors: even
+/// though there are no StartRead or StartWrite operations to queue before the
+/// call (that is part of the unary call itself) and there is no reactor object
+/// being created as a result of this call, we keep a consistent 2-phase
+/// initiation API among all the reactor flavors.
+class ClientUnaryReactor {
+ public:
+  virtual ~ClientUnaryReactor() {}
+
+  void StartCall() { call_->StartCall(); }
+  virtual void OnDone(const Status& s) {}
+  virtual void OnReadInitialMetadataDone(bool ok) {}
+
+ private:
+  friend class ClientCallbackUnary;
+  void BindCall(ClientCallbackUnary* call) { call_ = call; }
+  ClientCallbackUnary* call_;
+};
+
+// Define function out-of-line from class to avoid forward declaration issue
+inline void ClientCallbackUnary::BindReactor(ClientUnaryReactor* reactor) {
+  reactor->BindCall(this);
+}
+
 }  // namespace experimental
 
 namespace internal {
@@ -512,9 +556,9 @@
     this->BindReactor(reactor);
   }
 
-  ClientContext* context_;
+  ClientContext* const context_;
   Call call_;
-  ::grpc::experimental::ClientBidiReactor<Request, Response>* reactor_;
+  ::grpc::experimental::ClientBidiReactor<Request, Response>* const reactor_;
 
   CallOpSet<CallOpSendInitialMetadata, CallOpRecvInitialMetadata> start_ops_;
   CallbackWithSuccessTag start_tag_;
@@ -651,9 +695,9 @@
     start_ops_.ClientSendClose();
   }
 
-  ClientContext* context_;
+  ClientContext* const context_;
   Call call_;
-  ::grpc::experimental::ClientReadReactor<Response>* reactor_;
+  ::grpc::experimental::ClientReadReactor<Response>* const reactor_;
 
   CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage, CallOpClientSendClose,
             CallOpRecvInitialMetadata>
@@ -824,9 +868,9 @@
     finish_ops_.AllowNoMessage();
   }
 
-  ClientContext* context_;
+  ClientContext* const context_;
   Call call_;
-  ::grpc::experimental::ClientWriteReactor<Request>* reactor_;
+  ::grpc::experimental::ClientWriteReactor<Request>* const reactor_;
 
   CallOpSet<CallOpSendInitialMetadata, CallOpRecvInitialMetadata> start_ops_;
   CallbackWithSuccessTag start_tag_;
@@ -867,6 +911,109 @@
   }
 };
 
+class ClientCallbackUnaryImpl final
+    : public ::grpc::experimental::ClientCallbackUnary {
+ public:
+  // always allocated against a call arena, no memory free required
+  static void operator delete(void* ptr, std::size_t size) {
+    assert(size == sizeof(ClientCallbackUnaryImpl));
+  }
+
+  // This operator should never be called as the memory should be freed as part
+  // of the arena destruction. It only exists to provide a matching operator
+  // delete to the operator new so that some compilers will not complain (see
+  // https://github.com/grpc/grpc/issues/11301) Note at the time of adding this
+  // there are no tests catching the compiler warning.
+  static void operator delete(void*, void*) { assert(0); }
+
+  void StartCall() override {
+    // This call initiates two batches, each with a callback
+    // 1. Send initial metadata + write + writes done + recv initial metadata
+    // 2. Read message, recv trailing metadata
+    started_ = true;
+
+    start_tag_.Set(call_.call(),
+                   [this](bool ok) {
+                     reactor_->OnReadInitialMetadataDone(ok);
+                     MaybeFinish();
+                   },
+                   &start_ops_);
+    start_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
+                                   context_->initial_metadata_flags());
+    start_ops_.RecvInitialMetadata(context_);
+    start_ops_.set_core_cq_tag(&start_tag_);
+    call_.PerformOps(&start_ops_);
+
+    finish_tag_.Set(call_.call(), [this](bool ok) { MaybeFinish(); },
+                    &finish_ops_);
+    finish_ops_.ClientRecvStatus(context_, &finish_status_);
+    finish_ops_.set_core_cq_tag(&finish_tag_);
+    call_.PerformOps(&finish_ops_);
+  }
+
+  void MaybeFinish() {
+    if (--callbacks_outstanding_ == 0) {
+      Status s = std::move(finish_status_);
+      auto* reactor = reactor_;
+      auto* call = call_.call();
+      this->~ClientCallbackUnaryImpl();
+      g_core_codegen_interface->grpc_call_unref(call);
+      reactor->OnDone(s);
+    }
+  }
+
+ private:
+  friend class ClientCallbackUnaryFactory;
+
+  template <class Request, class Response>
+  ClientCallbackUnaryImpl(Call call, ClientContext* context, Request* request,
+                          Response* response,
+                          ::grpc::experimental::ClientUnaryReactor* reactor)
+      : context_(context), call_(call), reactor_(reactor) {
+    this->BindReactor(reactor);
+    // TODO(vjpai): don't assert
+    GPR_CODEGEN_ASSERT(start_ops_.SendMessagePtr(request).ok());
+    start_ops_.ClientSendClose();
+    finish_ops_.RecvMessage(response);
+    finish_ops_.AllowNoMessage();
+  }
+
+  ClientContext* const context_;
+  Call call_;
+  ::grpc::experimental::ClientUnaryReactor* const reactor_;
+
+  CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage, CallOpClientSendClose,
+            CallOpRecvInitialMetadata>
+      start_ops_;
+  CallbackWithSuccessTag start_tag_;
+
+  CallOpSet<CallOpGenericRecvMessage, CallOpClientRecvStatus> finish_ops_;
+  CallbackWithSuccessTag finish_tag_;
+  Status finish_status_;
+
+  // This call will have 2 callbacks: start and finish
+  std::atomic_int callbacks_outstanding_{2};
+  bool started_{false};
+};
+
+class ClientCallbackUnaryFactory {
+ public:
+  template <class Request, class Response>
+  static void Create(ChannelInterface* channel,
+                     const ::grpc::internal::RpcMethod& method,
+                     ClientContext* context, const Request* request,
+                     Response* response,
+                     ::grpc::experimental::ClientUnaryReactor* reactor) {
+    Call call = channel->CreateCall(method, context, channel->CallbackCQ());
+
+    g_core_codegen_interface->grpc_call_ref(call.call());
+
+    new (g_core_codegen_interface->grpc_call_arena_alloc(
+        call.call(), sizeof(ClientCallbackUnaryImpl)))
+        ClientCallbackUnaryImpl(call, context, request, response, reactor);
+  }
+};
+
 }  // namespace internal
 }  // namespace grpc
 
diff --git a/include/grpcpp/impl/codegen/client_context.h b/include/grpcpp/impl/codegen/client_context.h
index 5946488..999d8fc 100644
--- a/include/grpcpp/impl/codegen/client_context.h
+++ b/include/grpcpp/impl/codegen/client_context.h
@@ -51,17 +51,21 @@
 #include <grpcpp/impl/codegen/slice.h>
 #include <grpcpp/impl/codegen/status.h>
 #include <grpcpp/impl/codegen/string_ref.h>
+#include <grpcpp/impl/codegen/sync.h>
 #include <grpcpp/impl/codegen/time.h>
 
 struct census_context;
 struct grpc_call;
 
+namespace grpc_impl {
+
+class CallCredentials;
+class Channel;
+class CompletionQueue;
+}  // namespace grpc_impl
 namespace grpc {
 
-class Channel;
 class ChannelInterface;
-class CompletionQueue;
-class CallCredentials;
 class ClientContext;
 
 namespace internal {
@@ -78,6 +82,7 @@
 class ClientCallbackReaderImpl;
 template <class Request>
 class ClientCallbackWriterImpl;
+class ClientCallbackUnaryImpl;
 }  // namespace internal
 
 template <class R>
@@ -304,7 +309,8 @@
   /// call.
   ///
   /// \see  https://grpc.io/docs/guides/auth.html
-  void set_credentials(const std::shared_ptr<CallCredentials>& creds) {
+  void set_credentials(
+      const std::shared_ptr<grpc_impl::CallCredentials>& creds) {
     creds_ = creds;
   }
 
@@ -391,7 +397,7 @@
   friend class ::grpc::testing::InteropClientContextInspector;
   friend class ::grpc::internal::CallOpClientRecvStatus;
   friend class ::grpc::internal::CallOpRecvInitialMetadata;
-  friend class Channel;
+  friend class ::grpc_impl::Channel;
   template <class R>
   friend class ::grpc::ClientReader;
   template <class W>
@@ -416,6 +422,7 @@
   friend class ::grpc::internal::ClientCallbackReaderImpl;
   template <class Request>
   friend class ::grpc::internal::ClientCallbackWriterImpl;
+  friend class ::grpc::internal::ClientCallbackUnaryImpl;
 
   // Used by friend class CallOpClientRecvStatus
   void set_debug_error_string(const grpc::string& debug_error_string) {
@@ -423,7 +430,8 @@
   }
 
   grpc_call* call() const { return call_; }
-  void set_call(grpc_call* call, const std::shared_ptr<Channel>& channel);
+  void set_call(grpc_call* call,
+                const std::shared_ptr<::grpc_impl::Channel>& channel);
 
   experimental::ClientRpcInfo* set_client_rpc_info(
       const char* method, internal::RpcMethod::RpcType type,
@@ -456,13 +464,13 @@
   bool wait_for_ready_explicitly_set_;
   bool idempotent_;
   bool cacheable_;
-  std::shared_ptr<Channel> channel_;
-  std::mutex mu_;
+  std::shared_ptr<::grpc_impl::Channel> channel_;
+  grpc::internal::Mutex mu_;
   grpc_call* call_;
   bool call_canceled_;
   gpr_timespec deadline_;
   grpc::string authority_;
-  std::shared_ptr<CallCredentials> creds_;
+  std::shared_ptr<grpc_impl::CallCredentials> creds_;
   mutable std::shared_ptr<const AuthContext> auth_context_;
   struct census_context* census_context_;
   std::multimap<grpc::string, grpc::string> send_initial_metadata_;
diff --git a/include/grpcpp/impl/codegen/client_interceptor.h b/include/grpcpp/impl/codegen/client_interceptor.h
index 1910654..a4c85a7 100644
--- a/include/grpcpp/impl/codegen/client_interceptor.h
+++ b/include/grpcpp/impl/codegen/client_interceptor.h
@@ -26,10 +26,14 @@
 #include <grpcpp/impl/codegen/rpc_method.h>
 #include <grpcpp/impl/codegen/string_ref.h>
 
+namespace grpc_impl {
+
+class Channel;
+}
+
 namespace grpc {
 
 class ClientContext;
-class Channel;
 
 namespace internal {
 class InterceptorBatchMethodsImpl;
diff --git a/include/grpcpp/impl/codegen/client_unary_call.h b/include/grpcpp/impl/codegen/client_unary_call.h
index b9f8e16..e0f692b 100644
--- a/include/grpcpp/impl/codegen/client_unary_call.h
+++ b/include/grpcpp/impl/codegen/client_unary_call.h
@@ -27,9 +27,7 @@
 
 namespace grpc {
 
-class Channel;
 class ClientContext;
-class CompletionQueue;
 
 namespace internal {
 class RpcMethod;
diff --git a/include/grpcpp/impl/codegen/completion_queue.h b/include/grpcpp/impl/codegen/completion_queue.h
index 4812f02..f67a378 100644
--- a/include/grpcpp/impl/codegen/completion_queue.h
+++ b/include/grpcpp/impl/codegen/completion_queue.h
@@ -16,398 +16,15 @@
  *
  */
 
-/// A completion queue implements a concurrent producer-consumer queue, with
-/// two main API-exposed methods: \a Next and \a AsyncNext. These
-/// methods are the essential component of the gRPC C++ asynchronous API.
-/// There is also a \a Shutdown method to indicate that a given completion queue
-/// will no longer have regular events. This must be called before the
-/// completion queue is destroyed.
-/// All completion queue APIs are thread-safe and may be used concurrently with
-/// any other completion queue API invocation; it is acceptable to have
-/// multiple threads calling \a Next or \a AsyncNext on the same or different
-/// completion queues, or to call these methods concurrently with a \a Shutdown
-/// elsewhere.
-/// \remark{All other API calls on completion queue should be completed before
-/// a completion queue destructor is called.}
 #ifndef GRPCPP_IMPL_CODEGEN_COMPLETION_QUEUE_H
 #define GRPCPP_IMPL_CODEGEN_COMPLETION_QUEUE_H
 
-#include <grpc/impl/codegen/atm.h>
-#include <grpcpp/impl/codegen/completion_queue_tag.h>
-#include <grpcpp/impl/codegen/core_codegen_interface.h>
-#include <grpcpp/impl/codegen/grpc_library.h>
-#include <grpcpp/impl/codegen/status.h>
-#include <grpcpp/impl/codegen/time.h>
-
-struct grpc_completion_queue;
+#include <grpcpp/impl/codegen/completion_queue_impl.h>
 
 namespace grpc {
 
-template <class R>
-class ClientReader;
-template <class W>
-class ClientWriter;
-template <class W, class R>
-class ClientReaderWriter;
-template <class R>
-class ServerReader;
-template <class W>
-class ServerWriter;
-namespace internal {
-template <class W, class R>
-class ServerReaderWriterBody;
-}  // namespace internal
-
-class Channel;
-class ChannelInterface;
-class ClientContext;
-class CompletionQueue;
-class Server;
-class ServerBuilder;
-class ServerContext;
-class ServerInterface;
-
-namespace internal {
-class CompletionQueueTag;
-class RpcMethod;
-template <class ServiceType, class RequestType, class ResponseType>
-class RpcMethodHandler;
-template <class ServiceType, class RequestType, class ResponseType>
-class ClientStreamingHandler;
-template <class ServiceType, class RequestType, class ResponseType>
-class ServerStreamingHandler;
-template <class ServiceType, class RequestType, class ResponseType>
-class BidiStreamingHandler;
-template <class Streamer, bool WriteNeeded>
-class TemplatedBidiStreamingHandler;
-template <StatusCode code>
-class ErrorMethodHandler;
-template <class InputMessage, class OutputMessage>
-class BlockingUnaryCallImpl;
-template <class Op1, class Op2, class Op3, class Op4, class Op5, class Op6>
-class CallOpSet;
-}  // namespace internal
-
-extern CoreCodegenInterface* g_core_codegen_interface;
-
-/// A thin wrapper around \ref grpc_completion_queue (see \ref
-/// src/core/lib/surface/completion_queue.h).
-/// See \ref doc/cpp/perf_notes.md for notes on best practices for high
-/// performance servers.
-class CompletionQueue : private GrpcLibraryCodegen {
- public:
-  /// Default constructor. Implicitly creates a \a grpc_completion_queue
-  /// instance.
-  CompletionQueue()
-      : CompletionQueue(grpc_completion_queue_attributes{
-            GRPC_CQ_CURRENT_VERSION, GRPC_CQ_NEXT, GRPC_CQ_DEFAULT_POLLING,
-            nullptr}) {}
-
-  /// Wrap \a take, taking ownership of the instance.
-  ///
-  /// \param take The completion queue instance to wrap. Ownership is taken.
-  explicit CompletionQueue(grpc_completion_queue* take);
-
-  /// Destructor. Destroys the owned wrapped completion queue / instance.
-  ~CompletionQueue() {
-    g_core_codegen_interface->grpc_completion_queue_destroy(cq_);
-  }
-
-  /// Tri-state return for AsyncNext: SHUTDOWN, GOT_EVENT, TIMEOUT.
-  enum NextStatus {
-    SHUTDOWN,   ///< The completion queue has been shutdown and fully-drained
-    GOT_EVENT,  ///< Got a new event; \a tag will be filled in with its
-                ///< associated value; \a ok indicating its success.
-    TIMEOUT     ///< deadline was reached.
-  };
-
-  /// Read from the queue, blocking until an event is available or the queue is
-  /// shutting down.
-  ///
-  /// \param tag [out] Updated to point to the read event's tag.
-  /// \param ok [out] true if read a successful event, false otherwise.
-  ///
-  /// Note that each tag sent to the completion queue (through RPC operations
-  /// or alarms) will be delivered out of the completion queue by a call to
-  /// Next (or a related method), regardless of whether the operation succeeded
-  /// or not. Success here means that this operation completed in the normal
-  /// valid manner.
-  ///
-  /// Server-side RPC request: \a ok indicates that the RPC has indeed
-  /// been started. If it is false, the server has been Shutdown
-  /// before this particular call got matched to an incoming RPC.
-  ///
-  /// Client-side StartCall/RPC invocation: \a ok indicates that the RPC is
-  /// going to go to the wire. If it is false, it not going to the wire. This
-  /// would happen if the channel is either permanently broken or
-  /// transiently broken but with the fail-fast option. (Note that async unary
-  /// RPCs don't post a CQ tag at this point, nor do client-streaming
-  /// or bidi-streaming RPCs that have the initial metadata corked option set.)
-  ///
-  /// Client-side Write, Client-side WritesDone, Server-side Write,
-  /// Server-side Finish, Server-side SendInitialMetadata (which is
-  /// typically included in Write or Finish when not done explicitly):
-  /// \a ok means that the data/metadata/status/etc is going to go to the
-  /// wire. If it is false, it not going to the wire because the call
-  /// is already dead (i.e., canceled, deadline expired, other side
-  /// dropped the channel, etc).
-  ///
-  /// Client-side Read, Server-side Read, Client-side
-  /// RecvInitialMetadata (which is typically included in Read if not
-  /// done explicitly): \a ok indicates whether there is a valid message
-  /// that got read. If not, you know that there are certainly no more
-  /// messages that can ever be read from this stream. For the client-side
-  /// operations, this only happens because the call is dead. For the
-  /// server-sider operation, though, this could happen because the client
-  /// has done a WritesDone already.
-  ///
-  /// Client-side Finish: \a ok should always be true
-  ///
-  /// Server-side AsyncNotifyWhenDone: \a ok should always be true
-  ///
-  /// Alarm: \a ok is true if it expired, false if it was canceled
-  ///
-  /// \return true if got an event, false if the queue is fully drained and
-  ///         shut down.
-  bool Next(void** tag, bool* ok) {
-    return (AsyncNextInternal(tag, ok,
-                              g_core_codegen_interface->gpr_inf_future(
-                                  GPR_CLOCK_REALTIME)) != SHUTDOWN);
-  }
-
-  /// Read from the queue, blocking up to \a deadline (or the queue's shutdown).
-  /// Both \a tag and \a ok are updated upon success (if an event is available
-  /// within the \a deadline).  A \a tag points to an arbitrary location usually
-  /// employed to uniquely identify an event.
-  ///
-  /// \param tag [out] Upon sucess, updated to point to the event's tag.
-  /// \param ok [out] Upon sucess, true if a successful event, false otherwise
-  ///        See documentation for CompletionQueue::Next for explanation of ok
-  /// \param deadline [in] How long to block in wait for an event.
-  ///
-  /// \return The type of event read.
-  template <typename T>
-  NextStatus AsyncNext(void** tag, bool* ok, const T& deadline) {
-    TimePoint<T> deadline_tp(deadline);
-    return AsyncNextInternal(tag, ok, deadline_tp.raw_time());
-  }
-
-  /// EXPERIMENTAL
-  /// First executes \a F, then reads from the queue, blocking up to
-  /// \a deadline (or the queue's shutdown).
-  /// Both \a tag and \a ok are updated upon success (if an event is available
-  /// within the \a deadline).  A \a tag points to an arbitrary location usually
-  /// employed to uniquely identify an event.
-  ///
-  /// \param f [in] Function to execute before calling AsyncNext on this queue.
-  /// \param tag [out] Upon sucess, updated to point to the event's tag.
-  /// \param ok [out] Upon sucess, true if read a regular event, false
-  /// otherwise.
-  /// \param deadline [in] How long to block in wait for an event.
-  ///
-  /// \return The type of event read.
-  template <typename T, typename F>
-  NextStatus DoThenAsyncNext(F&& f, void** tag, bool* ok, const T& deadline) {
-    CompletionQueueTLSCache cache = CompletionQueueTLSCache(this);
-    f();
-    if (cache.Flush(tag, ok)) {
-      return GOT_EVENT;
-    } else {
-      return AsyncNext(tag, ok, deadline);
-    }
-  }
-
-  /// Request the shutdown of the queue.
-  ///
-  /// \warning This method must be called at some point if this completion queue
-  /// is accessed with Next or AsyncNext. \a Next will not return false
-  /// until this method has been called and all pending tags have been drained.
-  /// (Likewise for \a AsyncNext returning \a NextStatus::SHUTDOWN .)
-  /// Only once either one of these methods does that (that is, once the queue
-  /// has been \em drained) can an instance of this class be destroyed.
-  /// Also note that applications must ensure that no work is enqueued on this
-  /// completion queue after this method is called.
-  void Shutdown();
-
-  /// Returns a \em raw pointer to the underlying \a grpc_completion_queue
-  /// instance.
-  ///
-  /// \warning Remember that the returned instance is owned. No transfer of
-  /// owership is performed.
-  grpc_completion_queue* cq() { return cq_; }
-
- protected:
-  /// Private constructor of CompletionQueue only visible to friend classes
-  CompletionQueue(const grpc_completion_queue_attributes& attributes) {
-    cq_ = g_core_codegen_interface->grpc_completion_queue_create(
-        g_core_codegen_interface->grpc_completion_queue_factory_lookup(
-            &attributes),
-        &attributes, NULL);
-    InitialAvalanching();  // reserve this for the future shutdown
-  }
-
- private:
-  // Friend synchronous wrappers so that they can access Pluck(), which is
-  // a semi-private API geared towards the synchronous implementation.
-  template <class R>
-  friend class ::grpc::ClientReader;
-  template <class W>
-  friend class ::grpc::ClientWriter;
-  template <class W, class R>
-  friend class ::grpc::ClientReaderWriter;
-  template <class R>
-  friend class ::grpc::ServerReader;
-  template <class W>
-  friend class ::grpc::ServerWriter;
-  template <class W, class R>
-  friend class ::grpc::internal::ServerReaderWriterBody;
-  template <class ServiceType, class RequestType, class ResponseType>
-  friend class ::grpc::internal::RpcMethodHandler;
-  template <class ServiceType, class RequestType, class ResponseType>
-  friend class ::grpc::internal::ClientStreamingHandler;
-  template <class ServiceType, class RequestType, class ResponseType>
-  friend class ::grpc::internal::ServerStreamingHandler;
-  template <class Streamer, bool WriteNeeded>
-  friend class ::grpc::internal::TemplatedBidiStreamingHandler;
-  template <StatusCode code>
-  friend class ::grpc::internal::ErrorMethodHandler;
-  friend class ::grpc::Server;
-  friend class ::grpc::ServerContext;
-  friend class ::grpc::ServerInterface;
-  template <class InputMessage, class OutputMessage>
-  friend class ::grpc::internal::BlockingUnaryCallImpl;
-
-  // Friends that need access to constructor for callback CQ
-  friend class ::grpc::Channel;
-
-  // For access to Register/CompleteAvalanching
-  template <class Op1, class Op2, class Op3, class Op4, class Op5, class Op6>
-  friend class ::grpc::internal::CallOpSet;
-
-  /// EXPERIMENTAL
-  /// Creates a Thread Local cache to store the first event
-  /// On this completion queue queued from this thread.  Once
-  /// initialized, it must be flushed on the same thread.
-  class CompletionQueueTLSCache {
-   public:
-    CompletionQueueTLSCache(CompletionQueue* cq);
-    ~CompletionQueueTLSCache();
-    bool Flush(void** tag, bool* ok);
-
-   private:
-    CompletionQueue* cq_;
-    bool flushed_;
-  };
-
-  NextStatus AsyncNextInternal(void** tag, bool* ok, gpr_timespec deadline);
-
-  /// Wraps \a grpc_completion_queue_pluck.
-  /// \warning Must not be mixed with calls to \a Next.
-  bool Pluck(internal::CompletionQueueTag* tag) {
-    auto deadline =
-        g_core_codegen_interface->gpr_inf_future(GPR_CLOCK_REALTIME);
-    while (true) {
-      auto ev = g_core_codegen_interface->grpc_completion_queue_pluck(
-          cq_, tag, deadline, nullptr);
-      bool ok = ev.success != 0;
-      void* ignored = tag;
-      if (tag->FinalizeResult(&ignored, &ok)) {
-        GPR_CODEGEN_ASSERT(ignored == tag);
-        return ok;
-      }
-    }
-  }
-
-  /// Performs a single polling pluck on \a tag.
-  /// \warning Must not be mixed with calls to \a Next.
-  ///
-  /// TODO: sreek - This calls tag->FinalizeResult() even if the cq_ is already
-  /// shutdown. This is most likely a bug and if it is a bug, then change this
-  /// implementation to simple call the other TryPluck function with a zero
-  /// timeout. i.e:
-  ///      TryPluck(tag, gpr_time_0(GPR_CLOCK_REALTIME))
-  void TryPluck(internal::CompletionQueueTag* tag) {
-    auto deadline = g_core_codegen_interface->gpr_time_0(GPR_CLOCK_REALTIME);
-    auto ev = g_core_codegen_interface->grpc_completion_queue_pluck(
-        cq_, tag, deadline, nullptr);
-    if (ev.type == GRPC_QUEUE_TIMEOUT) return;
-    bool ok = ev.success != 0;
-    void* ignored = tag;
-    // the tag must be swallowed if using TryPluck
-    GPR_CODEGEN_ASSERT(!tag->FinalizeResult(&ignored, &ok));
-  }
-
-  /// Performs a single polling pluck on \a tag. Calls tag->FinalizeResult if
-  /// the pluck() was successful and returned the tag.
-  ///
-  /// This exects tag->FinalizeResult (if called) to return 'false' i.e expects
-  /// that the tag is internal not something that is returned to the user.
-  void TryPluck(internal::CompletionQueueTag* tag, gpr_timespec deadline) {
-    auto ev = g_core_codegen_interface->grpc_completion_queue_pluck(
-        cq_, tag, deadline, nullptr);
-    if (ev.type == GRPC_QUEUE_TIMEOUT || ev.type == GRPC_QUEUE_SHUTDOWN) {
-      return;
-    }
-
-    bool ok = ev.success != 0;
-    void* ignored = tag;
-    GPR_CODEGEN_ASSERT(!tag->FinalizeResult(&ignored, &ok));
-  }
-
-  /// Manage state of avalanching operations : completion queue tags that
-  /// trigger other completion queue operations. The underlying core completion
-  /// queue should not really shutdown until all avalanching operations have
-  /// been finalized. Note that we maintain the requirement that an avalanche
-  /// registration must take place before CQ shutdown (which must be maintained
-  /// elsehwere)
-  void InitialAvalanching() {
-    gpr_atm_rel_store(&avalanches_in_flight_, static_cast<gpr_atm>(1));
-  }
-  void RegisterAvalanching() {
-    gpr_atm_no_barrier_fetch_add(&avalanches_in_flight_,
-                                 static_cast<gpr_atm>(1));
-  }
-  void CompleteAvalanching() {
-    if (gpr_atm_no_barrier_fetch_add(&avalanches_in_flight_,
-                                     static_cast<gpr_atm>(-1)) == 1) {
-      g_core_codegen_interface->grpc_completion_queue_shutdown(cq_);
-    }
-  }
-
-  grpc_completion_queue* cq_;  // owned
-
-  gpr_atm avalanches_in_flight_;
-};
-
-/// A specific type of completion queue used by the processing of notifications
-/// by servers. Instantiated by \a ServerBuilder.
-class ServerCompletionQueue : public CompletionQueue {
- public:
-  bool IsFrequentlyPolled() { return polling_type_ != GRPC_CQ_NON_LISTENING; }
-
- protected:
-  /// Default constructor
-  ServerCompletionQueue() : polling_type_(GRPC_CQ_DEFAULT_POLLING) {}
-
- private:
-  /// \param completion_type indicates whether this is a NEXT or CALLBACK
-  /// completion queue.
-  /// \param polling_type Informs the GRPC library about the type of polling
-  /// allowed on this completion queue. See grpc_cq_polling_type's description
-  /// in grpc_types.h for more details.
-  /// \param shutdown_cb is the shutdown callback used for CALLBACK api queues
-  ServerCompletionQueue(grpc_cq_completion_type completion_type,
-                        grpc_cq_polling_type polling_type,
-                        grpc_experimental_completion_queue_functor* shutdown_cb)
-      : CompletionQueue(grpc_completion_queue_attributes{
-            GRPC_CQ_CURRENT_VERSION, completion_type, polling_type,
-            shutdown_cb}),
-        polling_type_(polling_type) {}
-
-  grpc_cq_polling_type polling_type_;
-  friend class ServerBuilder;
-  friend class Server;
-};
+typedef ::grpc_impl::CompletionQueue CompletionQueue;
+typedef ::grpc_impl::ServerCompletionQueue ServerCompletionQueue;
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/impl/codegen/completion_queue_impl.h b/include/grpcpp/impl/codegen/completion_queue_impl.h
new file mode 100644
index 0000000..5435f2f
--- /dev/null
+++ b/include/grpcpp/impl/codegen/completion_queue_impl.h
@@ -0,0 +1,422 @@
+/*
+ *
+ * Copyright 2015-2016 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.
+ *
+ */
+
+/// A completion queue implements a concurrent producer-consumer queue, with
+/// two main API-exposed methods: \a Next and \a AsyncNext. These
+/// methods are the essential component of the gRPC C++ asynchronous API.
+/// There is also a \a Shutdown method to indicate that a given completion queue
+/// will no longer have regular events. This must be called before the
+/// completion queue is destroyed.
+/// All completion queue APIs are thread-safe and may be used concurrently with
+/// any other completion queue API invocation; it is acceptable to have
+/// multiple threads calling \a Next or \a AsyncNext on the same or different
+/// completion queues, or to call these methods concurrently with a \a Shutdown
+/// elsewhere.
+/// \remark{All other API calls on completion queue should be completed before
+/// a completion queue destructor is called.}
+#ifndef GRPCPP_IMPL_CODEGEN_COMPLETION_QUEUE_IMPL_H
+#define GRPCPP_IMPL_CODEGEN_COMPLETION_QUEUE_IMPL_H
+
+#include <grpc/impl/codegen/atm.h>
+#include <grpcpp/impl/codegen/completion_queue_tag.h>
+#include <grpcpp/impl/codegen/core_codegen_interface.h>
+#include <grpcpp/impl/codegen/grpc_library.h>
+#include <grpcpp/impl/codegen/status.h>
+#include <grpcpp/impl/codegen/time.h>
+
+struct grpc_completion_queue;
+
+namespace grpc_impl {
+
+class Channel;
+class Server;
+class ServerBuilder;
+}  // namespace grpc_impl
+namespace grpc {
+
+template <class R>
+class ClientReader;
+template <class W>
+class ClientWriter;
+template <class W, class R>
+class ClientReaderWriter;
+template <class R>
+class ServerReader;
+template <class W>
+class ServerWriter;
+namespace internal {
+template <class W, class R>
+class ServerReaderWriterBody;
+}  // namespace internal
+
+class ChannelInterface;
+class ClientContext;
+class ServerContext;
+class ServerInterface;
+
+namespace internal {
+class CompletionQueueTag;
+class RpcMethod;
+template <class ServiceType, class RequestType, class ResponseType>
+class RpcMethodHandler;
+template <class ServiceType, class RequestType, class ResponseType>
+class ClientStreamingHandler;
+template <class ServiceType, class RequestType, class ResponseType>
+class ServerStreamingHandler;
+template <class ServiceType, class RequestType, class ResponseType>
+class BidiStreamingHandler;
+template <class Streamer, bool WriteNeeded>
+class TemplatedBidiStreamingHandler;
+template <StatusCode code>
+class ErrorMethodHandler;
+template <class InputMessage, class OutputMessage>
+class BlockingUnaryCallImpl;
+template <class Op1, class Op2, class Op3, class Op4, class Op5, class Op6>
+class CallOpSet;
+}  // namespace internal
+
+extern CoreCodegenInterface* g_core_codegen_interface;
+
+}  // namespace grpc
+
+namespace grpc_impl {
+
+/// A thin wrapper around \ref grpc_completion_queue (see \ref
+/// src/core/lib/surface/completion_queue.h).
+/// See \ref doc/cpp/perf_notes.md for notes on best practices for high
+/// performance servers.
+class CompletionQueue : private ::grpc::GrpcLibraryCodegen {
+ public:
+  /// Default constructor. Implicitly creates a \a grpc_completion_queue
+  /// instance.
+  CompletionQueue()
+      : CompletionQueue(grpc_completion_queue_attributes{
+            GRPC_CQ_CURRENT_VERSION, GRPC_CQ_NEXT, GRPC_CQ_DEFAULT_POLLING,
+            nullptr}) {}
+
+  /// Wrap \a take, taking ownership of the instance.
+  ///
+  /// \param take The completion queue instance to wrap. Ownership is taken.
+  explicit CompletionQueue(grpc_completion_queue* take);
+
+  /// Destructor. Destroys the owned wrapped completion queue / instance.
+  ~CompletionQueue() {
+    ::grpc::g_core_codegen_interface->grpc_completion_queue_destroy(cq_);
+  }
+
+  /// Tri-state return for AsyncNext: SHUTDOWN, GOT_EVENT, TIMEOUT.
+  enum NextStatus {
+    SHUTDOWN,   ///< The completion queue has been shutdown and fully-drained
+    GOT_EVENT,  ///< Got a new event; \a tag will be filled in with its
+                ///< associated value; \a ok indicating its success.
+    TIMEOUT     ///< deadline was reached.
+  };
+
+  /// Read from the queue, blocking until an event is available or the queue is
+  /// shutting down.
+  ///
+  /// \param tag [out] Updated to point to the read event's tag.
+  /// \param ok [out] true if read a successful event, false otherwise.
+  ///
+  /// Note that each tag sent to the completion queue (through RPC operations
+  /// or alarms) will be delivered out of the completion queue by a call to
+  /// Next (or a related method), regardless of whether the operation succeeded
+  /// or not. Success here means that this operation completed in the normal
+  /// valid manner.
+  ///
+  /// Server-side RPC request: \a ok indicates that the RPC has indeed
+  /// been started. If it is false, the server has been Shutdown
+  /// before this particular call got matched to an incoming RPC.
+  ///
+  /// Client-side StartCall/RPC invocation: \a ok indicates that the RPC is
+  /// going to go to the wire. If it is false, it not going to the wire. This
+  /// would happen if the channel is either permanently broken or
+  /// transiently broken but with the fail-fast option. (Note that async unary
+  /// RPCs don't post a CQ tag at this point, nor do client-streaming
+  /// or bidi-streaming RPCs that have the initial metadata corked option set.)
+  ///
+  /// Client-side Write, Client-side WritesDone, Server-side Write,
+  /// Server-side Finish, Server-side SendInitialMetadata (which is
+  /// typically included in Write or Finish when not done explicitly):
+  /// \a ok means that the data/metadata/status/etc is going to go to the
+  /// wire. If it is false, it not going to the wire because the call
+  /// is already dead (i.e., canceled, deadline expired, other side
+  /// dropped the channel, etc).
+  ///
+  /// Client-side Read, Server-side Read, Client-side
+  /// RecvInitialMetadata (which is typically included in Read if not
+  /// done explicitly): \a ok indicates whether there is a valid message
+  /// that got read. If not, you know that there are certainly no more
+  /// messages that can ever be read from this stream. For the client-side
+  /// operations, this only happens because the call is dead. For the
+  /// server-sider operation, though, this could happen because the client
+  /// has done a WritesDone already.
+  ///
+  /// Client-side Finish: \a ok should always be true
+  ///
+  /// Server-side AsyncNotifyWhenDone: \a ok should always be true
+  ///
+  /// Alarm: \a ok is true if it expired, false if it was canceled
+  ///
+  /// \return true if got an event, false if the queue is fully drained and
+  ///         shut down.
+  bool Next(void** tag, bool* ok) {
+    return (AsyncNextInternal(tag, ok,
+                              ::grpc::g_core_codegen_interface->gpr_inf_future(
+                                  GPR_CLOCK_REALTIME)) != SHUTDOWN);
+  }
+
+  /// Read from the queue, blocking up to \a deadline (or the queue's shutdown).
+  /// Both \a tag and \a ok are updated upon success (if an event is available
+  /// within the \a deadline).  A \a tag points to an arbitrary location usually
+  /// employed to uniquely identify an event.
+  ///
+  /// \param tag [out] Upon sucess, updated to point to the event's tag.
+  /// \param ok [out] Upon sucess, true if a successful event, false otherwise
+  ///        See documentation for CompletionQueue::Next for explanation of ok
+  /// \param deadline [in] How long to block in wait for an event.
+  ///
+  /// \return The type of event read.
+  template <typename T>
+  NextStatus AsyncNext(void** tag, bool* ok, const T& deadline) {
+    ::grpc::TimePoint<T> deadline_tp(deadline);
+    return AsyncNextInternal(tag, ok, deadline_tp.raw_time());
+  }
+
+  /// EXPERIMENTAL
+  /// First executes \a F, then reads from the queue, blocking up to
+  /// \a deadline (or the queue's shutdown).
+  /// Both \a tag and \a ok are updated upon success (if an event is available
+  /// within the \a deadline).  A \a tag points to an arbitrary location usually
+  /// employed to uniquely identify an event.
+  ///
+  /// \param f [in] Function to execute before calling AsyncNext on this queue.
+  /// \param tag [out] Upon sucess, updated to point to the event's tag.
+  /// \param ok [out] Upon sucess, true if read a regular event, false
+  /// otherwise.
+  /// \param deadline [in] How long to block in wait for an event.
+  ///
+  /// \return The type of event read.
+  template <typename T, typename F>
+  NextStatus DoThenAsyncNext(F&& f, void** tag, bool* ok, const T& deadline) {
+    CompletionQueueTLSCache cache = CompletionQueueTLSCache(this);
+    f();
+    if (cache.Flush(tag, ok)) {
+      return GOT_EVENT;
+    } else {
+      return AsyncNext(tag, ok, deadline);
+    }
+  }
+
+  /// Request the shutdown of the queue.
+  ///
+  /// \warning This method must be called at some point if this completion queue
+  /// is accessed with Next or AsyncNext. \a Next will not return false
+  /// until this method has been called and all pending tags have been drained.
+  /// (Likewise for \a AsyncNext returning \a NextStatus::SHUTDOWN .)
+  /// Only once either one of these methods does that (that is, once the queue
+  /// has been \em drained) can an instance of this class be destroyed.
+  /// Also note that applications must ensure that no work is enqueued on this
+  /// completion queue after this method is called.
+  void Shutdown();
+
+  /// Returns a \em raw pointer to the underlying \a grpc_completion_queue
+  /// instance.
+  ///
+  /// \warning Remember that the returned instance is owned. No transfer of
+  /// owership is performed.
+  grpc_completion_queue* cq() { return cq_; }
+
+ protected:
+  /// Private constructor of CompletionQueue only visible to friend classes
+  CompletionQueue(const grpc_completion_queue_attributes& attributes) {
+    cq_ = ::grpc::g_core_codegen_interface->grpc_completion_queue_create(
+        ::grpc::g_core_codegen_interface->grpc_completion_queue_factory_lookup(
+            &attributes),
+        &attributes, NULL);
+    InitialAvalanching();  // reserve this for the future shutdown
+  }
+
+ private:
+  // Friend synchronous wrappers so that they can access Pluck(), which is
+  // a semi-private API geared towards the synchronous implementation.
+  template <class R>
+  friend class ::grpc::ClientReader;
+  template <class W>
+  friend class ::grpc::ClientWriter;
+  template <class W, class R>
+  friend class ::grpc::ClientReaderWriter;
+  template <class R>
+  friend class ::grpc::ServerReader;
+  template <class W>
+  friend class ::grpc::ServerWriter;
+  template <class W, class R>
+  friend class ::grpc::internal::ServerReaderWriterBody;
+  template <class ServiceType, class RequestType, class ResponseType>
+  friend class ::grpc::internal::RpcMethodHandler;
+  template <class ServiceType, class RequestType, class ResponseType>
+  friend class ::grpc::internal::ClientStreamingHandler;
+  template <class ServiceType, class RequestType, class ResponseType>
+  friend class ::grpc::internal::ServerStreamingHandler;
+  template <class Streamer, bool WriteNeeded>
+  friend class ::grpc::internal::TemplatedBidiStreamingHandler;
+  template <::grpc::StatusCode code>
+  friend class ::grpc::internal::ErrorMethodHandler;
+  friend class ::grpc_impl::Server;
+  friend class ::grpc::ServerContext;
+  friend class ::grpc::ServerInterface;
+  template <class InputMessage, class OutputMessage>
+  friend class ::grpc::internal::BlockingUnaryCallImpl;
+
+  // Friends that need access to constructor for callback CQ
+  friend class ::grpc_impl::Channel;
+
+  // For access to Register/CompleteAvalanching
+  template <class Op1, class Op2, class Op3, class Op4, class Op5, class Op6>
+  friend class ::grpc::internal::CallOpSet;
+
+  /// EXPERIMENTAL
+  /// Creates a Thread Local cache to store the first event
+  /// On this completion queue queued from this thread.  Once
+  /// initialized, it must be flushed on the same thread.
+  class CompletionQueueTLSCache {
+   public:
+    CompletionQueueTLSCache(CompletionQueue* cq);
+    ~CompletionQueueTLSCache();
+    bool Flush(void** tag, bool* ok);
+
+   private:
+    CompletionQueue* cq_;
+    bool flushed_;
+  };
+
+  NextStatus AsyncNextInternal(void** tag, bool* ok, gpr_timespec deadline);
+
+  /// Wraps \a grpc_completion_queue_pluck.
+  /// \warning Must not be mixed with calls to \a Next.
+  bool Pluck(::grpc::internal::CompletionQueueTag* tag) {
+    auto deadline =
+        ::grpc::g_core_codegen_interface->gpr_inf_future(GPR_CLOCK_REALTIME);
+    while (true) {
+      auto ev = ::grpc::g_core_codegen_interface->grpc_completion_queue_pluck(
+          cq_, tag, deadline, nullptr);
+      bool ok = ev.success != 0;
+      void* ignored = tag;
+      if (tag->FinalizeResult(&ignored, &ok)) {
+        GPR_CODEGEN_ASSERT(ignored == tag);
+        return ok;
+      }
+    }
+  }
+
+  /// Performs a single polling pluck on \a tag.
+  /// \warning Must not be mixed with calls to \a Next.
+  ///
+  /// TODO: sreek - This calls tag->FinalizeResult() even if the cq_ is already
+  /// shutdown. This is most likely a bug and if it is a bug, then change this
+  /// implementation to simple call the other TryPluck function with a zero
+  /// timeout. i.e:
+  ///      TryPluck(tag, gpr_time_0(GPR_CLOCK_REALTIME))
+  void TryPluck(::grpc::internal::CompletionQueueTag* tag) {
+    auto deadline =
+        ::grpc::g_core_codegen_interface->gpr_time_0(GPR_CLOCK_REALTIME);
+    auto ev = ::grpc::g_core_codegen_interface->grpc_completion_queue_pluck(
+        cq_, tag, deadline, nullptr);
+    if (ev.type == GRPC_QUEUE_TIMEOUT) return;
+    bool ok = ev.success != 0;
+    void* ignored = tag;
+    // the tag must be swallowed if using TryPluck
+    GPR_CODEGEN_ASSERT(!tag->FinalizeResult(&ignored, &ok));
+  }
+
+  /// Performs a single polling pluck on \a tag. Calls tag->FinalizeResult if
+  /// the pluck() was successful and returned the tag.
+  ///
+  /// This exects tag->FinalizeResult (if called) to return 'false' i.e expects
+  /// that the tag is internal not something that is returned to the user.
+  void TryPluck(::grpc::internal::CompletionQueueTag* tag,
+                gpr_timespec deadline) {
+    auto ev = ::grpc::g_core_codegen_interface->grpc_completion_queue_pluck(
+        cq_, tag, deadline, nullptr);
+    if (ev.type == GRPC_QUEUE_TIMEOUT || ev.type == GRPC_QUEUE_SHUTDOWN) {
+      return;
+    }
+
+    bool ok = ev.success != 0;
+    void* ignored = tag;
+    GPR_CODEGEN_ASSERT(!tag->FinalizeResult(&ignored, &ok));
+  }
+
+  /// Manage state of avalanching operations : completion queue tags that
+  /// trigger other completion queue operations. The underlying core completion
+  /// queue should not really shutdown until all avalanching operations have
+  /// been finalized. Note that we maintain the requirement that an avalanche
+  /// registration must take place before CQ shutdown (which must be maintained
+  /// elsehwere)
+  void InitialAvalanching() {
+    gpr_atm_rel_store(&avalanches_in_flight_, static_cast<gpr_atm>(1));
+  }
+  void RegisterAvalanching() {
+    gpr_atm_no_barrier_fetch_add(&avalanches_in_flight_,
+                                 static_cast<gpr_atm>(1));
+  }
+  void CompleteAvalanching() {
+    if (gpr_atm_no_barrier_fetch_add(&avalanches_in_flight_,
+                                     static_cast<gpr_atm>(-1)) == 1) {
+      ::grpc::g_core_codegen_interface->grpc_completion_queue_shutdown(cq_);
+    }
+  }
+
+  grpc_completion_queue* cq_;  // owned
+
+  gpr_atm avalanches_in_flight_;
+};
+
+/// A specific type of completion queue used by the processing of notifications
+/// by servers. Instantiated by \a ServerBuilder.
+class ServerCompletionQueue : public CompletionQueue {
+ public:
+  bool IsFrequentlyPolled() { return polling_type_ != GRPC_CQ_NON_LISTENING; }
+
+ protected:
+  /// Default constructor
+  ServerCompletionQueue() : polling_type_(GRPC_CQ_DEFAULT_POLLING) {}
+
+ private:
+  /// \param completion_type indicates whether this is a NEXT or CALLBACK
+  /// completion queue.
+  /// \param polling_type Informs the GRPC library about the type of polling
+  /// allowed on this completion queue. See grpc_cq_polling_type's description
+  /// in grpc_types.h for more details.
+  /// \param shutdown_cb is the shutdown callback used for CALLBACK api queues
+  ServerCompletionQueue(grpc_cq_completion_type completion_type,
+                        grpc_cq_polling_type polling_type,
+                        grpc_experimental_completion_queue_functor* shutdown_cb)
+      : CompletionQueue(grpc_completion_queue_attributes{
+            GRPC_CQ_CURRENT_VERSION, completion_type, polling_type,
+            shutdown_cb}),
+        polling_type_(polling_type) {}
+
+  grpc_cq_polling_type polling_type_;
+  friend class ::grpc_impl::ServerBuilder;
+  friend class ::grpc_impl::Server;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_IMPL_CODEGEN_COMPLETION_QUEUE_IMPL_H
diff --git a/include/grpcpp/impl/codegen/intercepted_channel.h b/include/grpcpp/impl/codegen/intercepted_channel.h
index 5255a6d..cd0fcc0 100644
--- a/include/grpcpp/impl/codegen/intercepted_channel.h
+++ b/include/grpcpp/impl/codegen/intercepted_channel.h
@@ -21,6 +21,10 @@
 
 #include <grpcpp/impl/codegen/channel_interface.h>
 
+namespace grpc_impl {
+class CompletionQueue;
+}
+
 namespace grpc {
 
 namespace internal {
@@ -46,7 +50,7 @@
       : channel_(channel), interceptor_pos_(pos) {}
 
   Call CreateCall(const RpcMethod& method, ClientContext* context,
-                  CompletionQueue* cq) override {
+                  ::grpc_impl::CompletionQueue* cq) override {
     return channel_->CreateCallInternal(method, context, cq, interceptor_pos_);
   }
 
@@ -58,7 +62,8 @@
   }
 
   void NotifyOnStateChangeImpl(grpc_connectivity_state last_observed,
-                               gpr_timespec deadline, CompletionQueue* cq,
+                               gpr_timespec deadline,
+                               ::grpc_impl::CompletionQueue* cq,
                                void* tag) override {
     return channel_->NotifyOnStateChangeImpl(last_observed, deadline, cq, tag);
   }
@@ -67,7 +72,9 @@
     return channel_->WaitForStateChangeImpl(last_observed, deadline);
   }
 
-  CompletionQueue* CallbackCQ() override { return channel_->CallbackCQ(); }
+  ::grpc_impl::CompletionQueue* CallbackCQ() override {
+    return channel_->CallbackCQ();
+  }
 
   ChannelInterface* channel_;
   size_t interceptor_pos_;
diff --git a/include/grpcpp/impl/codegen/message_allocator.h b/include/grpcpp/impl/codegen/message_allocator.h
new file mode 100644
index 0000000..107bec6
--- /dev/null
+++ b/include/grpcpp/impl/codegen/message_allocator.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * Copyright 2019 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 GRPCPP_IMPL_CODEGEN_MESSAGE_ALLOCATOR_H
+#define GRPCPP_IMPL_CODEGEN_MESSAGE_ALLOCATOR_H
+
+namespace grpc {
+namespace experimental {
+
+// This is per rpc struct for the allocator. We can potentially put the grpc
+// call arena in here in the future.
+template <typename RequestT, typename ResponseT>
+struct RpcAllocatorInfo {
+  RequestT* request;
+  ResponseT* response;
+  // per rpc allocator internal state. MessageAllocator can set it when
+  // AllocateMessages is called and use it later.
+  void* allocator_state;
+};
+
+// Implementations need to be thread-safe
+template <typename RequestT, typename ResponseT>
+class MessageAllocator {
+ public:
+  virtual ~MessageAllocator() = default;
+  // Allocate both request and response
+  virtual void AllocateMessages(
+      RpcAllocatorInfo<RequestT, ResponseT>* info) = 0;
+  // Optional: deallocate request early, called by
+  // ServerCallbackRpcController::ReleaseRequest
+  virtual void DeallocateRequest(RpcAllocatorInfo<RequestT, ResponseT>* info) {}
+  // Deallocate response and request (if applicable)
+  virtual void DeallocateMessages(
+      RpcAllocatorInfo<RequestT, ResponseT>* info) = 0;
+};
+
+}  // namespace experimental
+}  // namespace grpc
+
+#endif  // GRPCPP_IMPL_CODEGEN_MESSAGE_ALLOCATOR_H
diff --git a/include/grpcpp/impl/codegen/method_handler_impl.h b/include/grpcpp/impl/codegen/method_handler_impl.h
index 0942862..dee1cb5 100644
--- a/include/grpcpp/impl/codegen/method_handler_impl.h
+++ b/include/grpcpp/impl/codegen/method_handler_impl.h
@@ -86,8 +86,8 @@
     param.call->cq()->Pluck(&ops);
   }
 
-  void* Deserialize(grpc_call* call, grpc_byte_buffer* req,
-                    Status* status) final {
+  void* Deserialize(grpc_call* call, grpc_byte_buffer* req, Status* status,
+                    void** handler_data) final {
     ByteBuffer buf;
     buf.set_buffer(req);
     auto* request = new (g_core_codegen_interface->grpc_call_arena_alloc(
@@ -191,8 +191,8 @@
     param.call->cq()->Pluck(&ops);
   }
 
-  void* Deserialize(grpc_call* call, grpc_byte_buffer* req,
-                    Status* status) final {
+  void* Deserialize(grpc_call* call, grpc_byte_buffer* req, Status* status,
+                    void** handler_data) final {
     ByteBuffer buf;
     buf.set_buffer(req);
     auto* request = new (g_core_codegen_interface->grpc_call_arena_alloc(
@@ -327,8 +327,8 @@
     param.call->cq()->Pluck(&ops);
   }
 
-  void* Deserialize(grpc_call* call, grpc_byte_buffer* req,
-                    Status* status) final {
+  void* Deserialize(grpc_call* call, grpc_byte_buffer* req, Status* status,
+                    void** handler_data) final {
     // We have to destroy any request payload
     if (req != nullptr) {
       g_core_codegen_interface->grpc_byte_buffer_destroy(req);
diff --git a/include/grpcpp/impl/codegen/rpc_service_method.h b/include/grpcpp/impl/codegen/rpc_service_method.h
index 56df61c..21fb2ac 100644
--- a/include/grpcpp/impl/codegen/rpc_service_method.h
+++ b/include/grpcpp/impl/codegen/rpc_service_method.h
@@ -46,21 +46,25 @@
     /// \param context : the ServerContext structure for this server call
     /// \param req : the request payload, if appropriate for this RPC
     /// \param req_status : the request status after any interceptors have run
+    /// \param handler_data: internal data for the handler.
     /// \param requester : used only by the callback API. It is a function
     ///        called by the RPC Controller to request another RPC (and also
     ///        to set up the state required to make that request possible)
     HandlerParameter(Call* c, ServerContext* context, void* req,
-                     Status req_status, std::function<void()> requester)
+                     Status req_status, void* handler_data,
+                     std::function<void()> requester)
         : call(c),
           server_context(context),
           request(req),
           status(req_status),
+          internal_data(handler_data),
           call_requester(std::move(requester)) {}
     ~HandlerParameter() {}
     Call* call;
     ServerContext* server_context;
     void* request;
     Status status;
+    void* internal_data;
     std::function<void()> call_requester;
   };
   virtual void RunHandler(const HandlerParameter& param) = 0;
@@ -71,7 +75,7 @@
      pointer after calling RunHandler. Ownership of the deserialized request is
      retained by the handler. Returns nullptr if deserialization failed. */
   virtual void* Deserialize(grpc_call* call, grpc_byte_buffer* req,
-                            Status* status) {
+                            Status* status, void** handler_data) {
     GPR_CODEGEN_ASSERT(req == nullptr);
     return nullptr;
   }
diff --git a/include/grpcpp/impl/codegen/server_callback.h b/include/grpcpp/impl/codegen/server_callback.h
index 87edea8..3f6d5cd 100644
--- a/include/grpcpp/impl/codegen/server_callback.h
+++ b/include/grpcpp/impl/codegen/server_callback.h
@@ -28,6 +28,7 @@
 #include <grpcpp/impl/codegen/callback_common.h>
 #include <grpcpp/impl/codegen/config.h>
 #include <grpcpp/impl/codegen/core_codegen_interface.h>
+#include <grpcpp/impl/codegen/message_allocator.h>
 #include <grpcpp/impl/codegen/server_context.h>
 #include <grpcpp/impl/codegen/server_interface.h>
 #include <grpcpp/impl/codegen/status.h>
@@ -37,11 +38,43 @@
 // Declare base class of all reactors as internal
 namespace internal {
 
+// Forward declarations
+template <class Request, class Response>
+class CallbackClientStreamingHandler;
+template <class Request, class Response>
+class CallbackServerStreamingHandler;
+template <class Request, class Response>
+class CallbackBidiHandler;
+
 class ServerReactor {
  public:
   virtual ~ServerReactor() = default;
   virtual void OnDone() = 0;
   virtual void OnCancel() = 0;
+
+ private:
+  friend class ::grpc::ServerContext;
+  template <class Request, class Response>
+  friend class CallbackClientStreamingHandler;
+  template <class Request, class Response>
+  friend class CallbackServerStreamingHandler;
+  template <class Request, class Response>
+  friend class CallbackBidiHandler;
+
+  // The ServerReactor is responsible for tracking when it is safe to call
+  // OnCancel. This function should not be called until after OnStarted is done
+  // and the RPC has completed with a cancellation. This is tracked by counting
+  // how many of these conditions have been met and calling OnCancel when none
+  // remain unmet.
+
+  void MaybeCallOnCancel() {
+    if (on_cancel_conditions_remaining_.fetch_sub(
+            1, std::memory_order_acq_rel) == 1) {
+      OnCancel();
+    }
+  }
+
+  std::atomic_int on_cancel_conditions_remaining_{2};
 };
 
 }  // namespace internal
@@ -103,6 +136,14 @@
   /// to be called before the callback completes.
   virtual void SetCancelCallback(std::function<void()> callback) = 0;
   virtual void ClearCancelCallback() = 0;
+
+  // NOTE: This is an API for advanced users who need custom allocators.
+  // Optionally deallocate request early to reduce the size of working set.
+  // A custom MessageAllocator needs to be registered to make use of this.
+  virtual void FreeRequest() = 0;
+  // NOTE: This is an API for advanced users who need custom allocators.
+  // Get and maybe mutate the allocator state associated with the current RPC.
+  virtual void* GetAllocatorState() = 0;
 };
 
 // NOTE: The actual streaming object classes are provided
@@ -253,7 +294,9 @@
   void Finish(Status s) { stream_->Finish(std::move(s)); }
 
   /// Notify the application that a streaming RPC has started and that it is now
-  /// ok to call any operation initation method.
+  /// ok to call any operation initiation method. An RPC is considered started
+  /// after the server has received all initial metadata from the client, which
+  /// is a result of the client calling StartCall().
   ///
   /// \param[in] context The context object now associated with this RPC
   virtual void OnStarted(ServerContext* context) {}
@@ -330,7 +373,7 @@
   ServerCallbackReader<Request>* reader_;
 };
 
-/// \a ServerReadReactor is the interface for a server-streaming RPC.
+/// \a ServerWriteReactor is the interface for a server-streaming RPC.
 template <class Request, class Response>
 class ServerWriteReactor : public internal::ServerReactor {
  public:
@@ -413,17 +456,24 @@
                          experimental::ServerCallbackRpcController*)>
           func)
       : func_(func) {}
+
+  void SetMessageAllocator(
+      experimental::MessageAllocator<RequestType, ResponseType>* allocator) {
+    allocator_ = allocator;
+  }
+
   void RunHandler(const HandlerParameter& param) final {
     // Arena allocate a controller structure (that includes request/response)
     g_core_codegen_interface->grpc_call_ref(param.call->call());
+    auto* allocator_info =
+        static_cast<experimental::RpcAllocatorInfo<RequestType, ResponseType>*>(
+            param.internal_data);
     auto* controller = new (g_core_codegen_interface->grpc_call_arena_alloc(
         param.call->call(), sizeof(ServerCallbackRpcControllerImpl)))
-        ServerCallbackRpcControllerImpl(
-            param.server_context, param.call,
-            static_cast<RequestType*>(param.request),
-            std::move(param.call_requester));
+        ServerCallbackRpcControllerImpl(param.server_context, param.call,
+                                        allocator_info, allocator_,
+                                        std::move(param.call_requester));
     Status status = param.status;
-
     if (status.ok()) {
       // Call the actual function handler and expect the user to call finish
       CatchingCallback(func_, param.server_context, controller->request(),
@@ -434,18 +484,41 @@
     }
   }
 
-  void* Deserialize(grpc_call* call, grpc_byte_buffer* req,
-                    Status* status) final {
+  void* Deserialize(grpc_call* call, grpc_byte_buffer* req, Status* status,
+                    void** handler_data) final {
     ByteBuffer buf;
     buf.set_buffer(req);
-    auto* request = new (g_core_codegen_interface->grpc_call_arena_alloc(
-        call, sizeof(RequestType))) RequestType();
+    RequestType* request = nullptr;
+    experimental::RpcAllocatorInfo<RequestType, ResponseType>* allocator_info =
+        new (g_core_codegen_interface->grpc_call_arena_alloc(
+            call, sizeof(*allocator_info)))
+            experimental::RpcAllocatorInfo<RequestType, ResponseType>();
+    if (allocator_ != nullptr) {
+      allocator_->AllocateMessages(allocator_info);
+    } else {
+      allocator_info->request =
+          new (g_core_codegen_interface->grpc_call_arena_alloc(
+              call, sizeof(RequestType))) RequestType();
+      allocator_info->response =
+          new (g_core_codegen_interface->grpc_call_arena_alloc(
+              call, sizeof(ResponseType))) ResponseType();
+    }
+    *handler_data = allocator_info;
+    request = allocator_info->request;
     *status = SerializationTraits<RequestType>::Deserialize(&buf, request);
     buf.Release();
     if (status->ok()) {
       return request;
     }
-    request->~RequestType();
+    // Clean up on deserialization failure.
+    if (allocator_ != nullptr) {
+      allocator_->DeallocateMessages(allocator_info);
+    } else {
+      allocator_info->request->~RequestType();
+      allocator_info->response->~ResponseType();
+      allocator_info->request = nullptr;
+      allocator_info->response = nullptr;
+    }
     return nullptr;
   }
 
@@ -453,6 +526,8 @@
   std::function<void(ServerContext*, const RequestType*, ResponseType*,
                      experimental::ServerCallbackRpcController*)>
       func_;
+  experimental::MessageAllocator<RequestType, ResponseType>* allocator_ =
+      nullptr;
 
   // The implementation class of ServerCallbackRpcController is a private member
   // of CallbackUnaryHandler since it is never exposed anywhere, and this allows
@@ -473,8 +548,9 @@
       }
       // The response is dropped if the status is not OK.
       if (s.ok()) {
-        finish_ops_.ServerSendStatus(&ctx_->trailing_metadata_,
-                                     finish_ops_.SendMessagePtr(&resp_));
+        finish_ops_.ServerSendStatus(
+            &ctx_->trailing_metadata_,
+            finish_ops_.SendMessagePtr(allocator_info_->response));
       } else {
         finish_ops_.ServerSendStatus(&ctx_->trailing_metadata_, s);
       }
@@ -512,28 +588,50 @@
 
     void ClearCancelCallback() override { ctx_->ClearCancelCallback(); }
 
+    void FreeRequest() override {
+      if (allocator_ != nullptr) {
+        allocator_->DeallocateRequest(allocator_info_);
+      }
+    }
+
+    void* GetAllocatorState() override {
+      return allocator_info_->allocator_state;
+    }
+
    private:
     friend class CallbackUnaryHandler<RequestType, ResponseType>;
 
-    ServerCallbackRpcControllerImpl(ServerContext* ctx, Call* call,
-                                    const RequestType* req,
-                                    std::function<void()> call_requester)
+    ServerCallbackRpcControllerImpl(
+        ServerContext* ctx, Call* call,
+        experimental::RpcAllocatorInfo<RequestType, ResponseType>*
+            allocator_info,
+        experimental::MessageAllocator<RequestType, ResponseType>* allocator,
+        std::function<void()> call_requester)
         : ctx_(ctx),
           call_(*call),
-          req_(req),
+          allocator_info_(allocator_info),
+          allocator_(allocator),
           call_requester_(std::move(call_requester)) {
       ctx_->BeginCompletionOp(call, [this](bool) { MaybeDone(); }, nullptr);
     }
 
-    ~ServerCallbackRpcControllerImpl() { req_->~RequestType(); }
-
-    const RequestType* request() { return req_; }
-    ResponseType* response() { return &resp_; }
+    const RequestType* request() { return allocator_info_->request; }
+    ResponseType* response() { return allocator_info_->response; }
 
     void MaybeDone() {
       if (--callbacks_outstanding_ == 0) {
         grpc_call* call = call_.call();
         auto call_requester = std::move(call_requester_);
+        if (allocator_ != nullptr) {
+          allocator_->DeallocateMessages(allocator_info_);
+        } else {
+          if (allocator_info_->request != nullptr) {
+            allocator_info_->request->~RequestType();
+          }
+          if (allocator_info_->response != nullptr) {
+            allocator_info_->response->~ResponseType();
+          }
+        }
         this->~ServerCallbackRpcControllerImpl();  // explicitly call destructor
         g_core_codegen_interface->grpc_call_unref(call);
         call_requester();
@@ -549,8 +647,8 @@
 
     ServerContext* ctx_;
     Call call_;
-    const RequestType* req_;
-    ResponseType resp_;
+    experimental::RpcAllocatorInfo<RequestType, ResponseType>* allocator_info_;
+    experimental::MessageAllocator<RequestType, ResponseType>* allocator_;
     std::function<void()> call_requester_;
     std::atomic_int callbacks_outstanding_{
         2};  // reserve for Finish and CompletionOp
@@ -588,6 +686,8 @@
 
     reader->BindReactor(reactor);
     reactor->OnStarted(param.server_context, reader->response());
+    // The earliest that OnCancel can be called is after OnStarted is done.
+    reactor->MaybeCallOnCancel();
     reader->MaybeDone();
   }
 
@@ -730,11 +830,13 @@
                                  std::move(param.call_requester), reactor);
     writer->BindReactor(reactor);
     reactor->OnStarted(param.server_context, writer->request());
+    // The earliest that OnCancel can be called is after OnStarted is done.
+    reactor->MaybeCallOnCancel();
     writer->MaybeDone();
   }
 
-  void* Deserialize(grpc_call* call, grpc_byte_buffer* req,
-                    Status* status) final {
+  void* Deserialize(grpc_call* call, grpc_byte_buffer* req, Status* status,
+                    void** handler_data) final {
     ByteBuffer buf;
     buf.set_buffer(req);
     auto* request = new (g_core_codegen_interface->grpc_call_arena_alloc(
@@ -906,6 +1008,8 @@
 
     stream->BindReactor(reactor);
     reactor->OnStarted(param.server_context);
+    // The earliest that OnCancel can be called is after OnStarted is done.
+    reactor->MaybeCallOnCancel();
     stream->MaybeDone();
   }
 
diff --git a/include/grpcpp/impl/codegen/server_context.h b/include/grpcpp/impl/codegen/server_context.h
index 591a9ff..c690374 100644
--- a/include/grpcpp/impl/codegen/server_context.h
+++ b/include/grpcpp/impl/codegen/server_context.h
@@ -41,11 +41,14 @@
 struct grpc_call;
 struct census_context;
 
+namespace grpc_impl {
+
+class CompletionQueue;
+class Server;
+}  // namespace grpc_impl
 namespace grpc {
 class ClientContext;
 class GenericServerContext;
-class CompletionQueue;
-class Server;
 class ServerInterface;
 template <class W, class R>
 class ServerAsyncReader;
@@ -87,6 +90,7 @@
 class ServerReactor;
 }  // namespace internal
 
+class ServerInterface;
 namespace testing {
 class InteropServerContextInspector;
 class ServerContextTestSpouse;
@@ -269,7 +273,7 @@
   friend class ::grpc::testing::InteropServerContextInspector;
   friend class ::grpc::testing::ServerContextTestSpouse;
   friend class ::grpc::ServerInterface;
-  friend class ::grpc::Server;
+  friend class ::grpc_impl::Server;
   template <class W, class R>
   friend class ::grpc::ServerAsyncReader;
   template <class W>
@@ -351,7 +355,7 @@
 
   gpr_timespec deadline_;
   grpc_call* call_;
-  CompletionQueue* cq_;
+  ::grpc_impl::CompletionQueue* cq_;
   bool sent_initial_metadata_;
   mutable std::shared_ptr<const AuthContext> auth_context_;
   mutable internal::MetadataMap client_metadata_;
diff --git a/include/grpcpp/impl/codegen/server_interface.h b/include/grpcpp/impl/codegen/server_interface.h
index f599e03..10dc6ab 100644
--- a/include/grpcpp/impl/codegen/server_interface.h
+++ b/include/grpcpp/impl/codegen/server_interface.h
@@ -28,14 +28,18 @@
 #include <grpcpp/impl/codegen/rpc_service_method.h>
 #include <grpcpp/impl/codegen/server_context.h>
 
+namespace grpc_impl {
+
+class CompletionQueue;
+class ServerCompletionQueue;
+class Channel;
+class ServerCredentials;
+}  // namespace grpc_impl
 namespace grpc {
 
 class AsyncGenericService;
-class Channel;
 class GenericServerContext;
-class ServerCompletionQueue;
 class ServerContext;
-class ServerCredentials;
 class Service;
 
 extern CoreCodegenInterface* g_core_codegen_interface;
@@ -146,11 +150,11 @@
   /// 192.168.1.1:31416, [::1]:27182, etc.).
   /// \params creds The credentials associated with the server.
   ///
-  /// \return bound port number on sucess, 0 on failure.
+  /// \return bound port number on success, 0 on failure.
   ///
   /// \warning It's an error to call this method on an already started server.
   virtual int AddListeningPort(const grpc::string& addr,
-                               ServerCredentials* creds) = 0;
+                               grpc_impl::ServerCredentials* creds) = 0;
 
   /// Start the server.
   ///
@@ -158,7 +162,8 @@
   /// caller is required to keep all completion queues live until the server is
   /// destroyed.
   /// \param num_cqs How many completion queues does \a cqs hold.
-  virtual void Start(ServerCompletionQueue** cqs, size_t num_cqs) = 0;
+  virtual void Start(::grpc_impl::ServerCompletionQueue** cqs,
+                     size_t num_cqs) = 0;
 
   virtual void ShutdownInternal(gpr_timespec deadline) = 0;
 
@@ -173,9 +178,9 @@
    public:
     BaseAsyncRequest(ServerInterface* server, ServerContext* context,
                      internal::ServerAsyncStreamingInterface* stream,
-                     CompletionQueue* call_cq,
-                     ServerCompletionQueue* notification_cq, void* tag,
-                     bool delete_on_finalize);
+                     ::grpc_impl::CompletionQueue* call_cq,
+                     ::grpc_impl::ServerCompletionQueue* notification_cq,
+                     void* tag, bool delete_on_finalize);
     virtual ~BaseAsyncRequest();
 
     bool FinalizeResult(void** tag, bool* status) override;
@@ -187,8 +192,8 @@
     ServerInterface* const server_;
     ServerContext* const context_;
     internal::ServerAsyncStreamingInterface* const stream_;
-    CompletionQueue* const call_cq_;
-    ServerCompletionQueue* const notification_cq_;
+    ::grpc_impl::CompletionQueue* const call_cq_;
+    ::grpc_impl::ServerCompletionQueue* const notification_cq_;
     void* const tag_;
     const bool delete_on_finalize_;
     grpc_call* call_;
@@ -202,16 +207,17 @@
    public:
     RegisteredAsyncRequest(ServerInterface* server, ServerContext* context,
                            internal::ServerAsyncStreamingInterface* stream,
-                           CompletionQueue* call_cq,
-                           ServerCompletionQueue* notification_cq, void* tag,
-                           const char* name, internal::RpcMethod::RpcType type);
+                           ::grpc_impl::CompletionQueue* call_cq,
+                           ::grpc_impl::ServerCompletionQueue* notification_cq,
+                           void* tag, const char* name,
+                           internal::RpcMethod::RpcType type);
 
     virtual bool FinalizeResult(void** tag, bool* status) override {
       /* If we are done intercepting, then there is nothing more for us to do */
       if (done_intercepting_) {
         return BaseAsyncRequest::FinalizeResult(tag, status);
       }
-      call_wrapper_ = internal::Call(
+      call_wrapper_ = ::grpc::internal::Call(
           call_, server_, call_cq_, server_->max_receive_message_size(),
           context_->set_server_rpc_info(name_, type_,
                                         *server_->interceptor_creators()));
@@ -220,7 +226,7 @@
 
    protected:
     void IssueRequest(void* registered_method, grpc_byte_buffer** payload,
-                      ServerCompletionQueue* notification_cq);
+                      ::grpc_impl::ServerCompletionQueue* notification_cq);
     const char* name_;
     const internal::RpcMethod::RpcType type_;
   };
@@ -230,8 +236,9 @@
     NoPayloadAsyncRequest(internal::RpcServiceMethod* registered_method,
                           ServerInterface* server, ServerContext* context,
                           internal::ServerAsyncStreamingInterface* stream,
-                          CompletionQueue* call_cq,
-                          ServerCompletionQueue* notification_cq, void* tag)
+                          ::grpc_impl::CompletionQueue* call_cq,
+                          ::grpc_impl::ServerCompletionQueue* notification_cq,
+                          void* tag)
         : RegisteredAsyncRequest(
               server, context, stream, call_cq, notification_cq, tag,
               registered_method->name(), registered_method->method_type()) {
@@ -247,9 +254,9 @@
     PayloadAsyncRequest(internal::RpcServiceMethod* registered_method,
                         ServerInterface* server, ServerContext* context,
                         internal::ServerAsyncStreamingInterface* stream,
-                        CompletionQueue* call_cq,
-                        ServerCompletionQueue* notification_cq, void* tag,
-                        Message* request)
+                        ::grpc_impl::CompletionQueue* call_cq,
+                        ::grpc_impl::ServerCompletionQueue* notification_cq,
+                        void* tag, Message* request)
         : RegisteredAsyncRequest(
               server, context, stream, call_cq, notification_cq, tag,
               registered_method->name(), registered_method->method_type()),
@@ -304,9 +311,9 @@
     ServerInterface* const server_;
     ServerContext* const context_;
     internal::ServerAsyncStreamingInterface* const stream_;
-    CompletionQueue* const call_cq_;
+    ::grpc_impl::CompletionQueue* const call_cq_;
 
-    ServerCompletionQueue* const notification_cq_;
+    ::grpc_impl::ServerCompletionQueue* const notification_cq_;
     void* const tag_;
     Message* const request_;
     ByteBuffer payload_;
@@ -316,9 +323,9 @@
    public:
     GenericAsyncRequest(ServerInterface* server, GenericServerContext* context,
                         internal::ServerAsyncStreamingInterface* stream,
-                        CompletionQueue* call_cq,
-                        ServerCompletionQueue* notification_cq, void* tag,
-                        bool delete_on_finalize);
+                        ::grpc_impl::CompletionQueue* call_cq,
+                        ::grpc_impl::ServerCompletionQueue* notification_cq,
+                        void* tag, bool delete_on_finalize);
 
     bool FinalizeResult(void** tag, bool* status) override;
 
@@ -330,9 +337,9 @@
   void RequestAsyncCall(internal::RpcServiceMethod* method,
                         ServerContext* context,
                         internal::ServerAsyncStreamingInterface* stream,
-                        CompletionQueue* call_cq,
-                        ServerCompletionQueue* notification_cq, void* tag,
-                        Message* message) {
+                        ::grpc_impl::CompletionQueue* call_cq,
+                        ::grpc_impl::ServerCompletionQueue* notification_cq,
+                        void* tag, Message* message) {
     GPR_CODEGEN_ASSERT(method);
     new PayloadAsyncRequest<Message>(method, this, context, stream, call_cq,
                                      notification_cq, tag, message);
@@ -341,18 +348,19 @@
   void RequestAsyncCall(internal::RpcServiceMethod* method,
                         ServerContext* context,
                         internal::ServerAsyncStreamingInterface* stream,
-                        CompletionQueue* call_cq,
-                        ServerCompletionQueue* notification_cq, void* tag) {
+                        ::grpc_impl::CompletionQueue* call_cq,
+                        ::grpc_impl::ServerCompletionQueue* notification_cq,
+                        void* tag) {
     GPR_CODEGEN_ASSERT(method);
     new NoPayloadAsyncRequest(method, this, context, stream, call_cq,
                               notification_cq, tag);
   }
 
-  void RequestAsyncGenericCall(GenericServerContext* context,
-                               internal::ServerAsyncStreamingInterface* stream,
-                               CompletionQueue* call_cq,
-                               ServerCompletionQueue* notification_cq,
-                               void* tag) {
+  void RequestAsyncGenericCall(
+      GenericServerContext* context,
+      internal::ServerAsyncStreamingInterface* stream,
+      ::grpc_impl::CompletionQueue* call_cq,
+      ::grpc_impl::ServerCompletionQueue* notification_cq, void* tag) {
     new GenericAsyncRequest(this, context, stream, call_cq, notification_cq,
                             tag, true);
   }
@@ -377,7 +385,7 @@
   // Returns nullptr (rather than being pure) since this is a post-1.0 method
   // and adding a new pure method to an interface would be a breaking change
   // (even though this is private and non-API)
-  virtual CompletionQueue* CallbackCQ() { return nullptr; }
+  virtual ::grpc_impl::CompletionQueue* CallbackCQ() { return nullptr; }
 };
 
 }  // namespace grpc
diff --git a/include/grpcpp/impl/codegen/service_type.h b/include/grpcpp/impl/codegen/service_type.h
index 332a04c..f1d1272 100644
--- a/include/grpcpp/impl/codegen/service_type.h
+++ b/include/grpcpp/impl/codegen/service_type.h
@@ -26,12 +26,14 @@
 #include <grpcpp/impl/codegen/server_interface.h>
 #include <grpcpp/impl/codegen/status.h>
 
+namespace grpc_impl {
+
+class Server;
+class CompletionQueue;
+}  // namespace grpc_impl
 namespace grpc {
 
-class CompletionQueue;
-class Server;
 class ServerInterface;
-class ServerCompletionQueue;
 class ServerContext;
 
 namespace internal {
@@ -132,6 +134,11 @@
           internal::RpcServiceMethod::ApiType::RAW_CALL_BACK);
     }
 
+    internal::MethodHandler* GetHandler(int index) {
+      size_t idx = static_cast<size_t>(index);
+      return service_->methods_[idx]->handler();
+    }
+
    private:
     Service* service_;
   };
@@ -228,7 +235,7 @@
   }
 
  private:
-  friend class Server;
+  friend class grpc_impl::Server;
   friend class ServerInterface;
   ServerInterface* server_;
   std::vector<std::unique_ptr<internal::RpcServiceMethod>> methods_;
diff --git a/include/grpcpp/impl/codegen/status_code_enum.h b/include/grpcpp/impl/codegen/status_code_enum.h
index 09943f1..bdd7ead 100644
--- a/include/grpcpp/impl/codegen/status_code_enum.h
+++ b/include/grpcpp/impl/codegen/status_code_enum.h
@@ -119,7 +119,8 @@
   INTERNAL = 13,
 
   /// The service is currently unavailable. This is a most likely a transient
-  /// condition and may be corrected by retrying with a backoff.
+  /// condition and may be corrected by retrying with a backoff. Note that it is
+  /// not always safe to retry non-idempotent operations.
   ///
   /// \warning Although data MIGHT not have been transmitted when this
   /// status occurs, there is NOT A GUARANTEE that the server has not seen
diff --git a/include/grpcpp/impl/codegen/sync.h b/include/grpcpp/impl/codegen/sync.h
new file mode 100644
index 0000000..146f182
--- /dev/null
+++ b/include/grpcpp/impl/codegen/sync.h
@@ -0,0 +1,151 @@
+/*
+ *
+ * Copyright 2019 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 GRPCPP_IMPL_CODEGEN_SYNC_H
+#define GRPCPP_IMPL_CODEGEN_SYNC_H
+
+#include <grpc/impl/codegen/port_platform.h>
+
+#ifdef GPR_HAS_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#include <mutex>
+
+#include <grpc/impl/codegen/log.h>
+#include <grpc/impl/codegen/sync.h>
+
+#include <grpcpp/impl/codegen/core_codegen_interface.h>
+
+// The core library is not accessible in C++ codegen headers, and vice versa.
+// Thus, we need to have duplicate headers with similar functionality.
+// Make sure any change to this file is also reflected in
+// src/core/lib/gprpp/sync.h too.
+//
+// Whenever possible, prefer "src/core/lib/gprpp/sync.h" over this file,
+// since in core we do not rely on g_core_codegen_interface and hence do not
+// pay the costs of virtual function calls.
+
+namespace grpc {
+namespace internal {
+
+class Mutex {
+ public:
+  Mutex() { g_core_codegen_interface->gpr_mu_init(&mu_); }
+  ~Mutex() { g_core_codegen_interface->gpr_mu_destroy(&mu_); }
+
+  Mutex(const Mutex&) = delete;
+  Mutex& operator=(const Mutex&) = delete;
+
+  gpr_mu* get() { return &mu_; }
+  const gpr_mu* get() const { return &mu_; }
+
+ private:
+  union {
+    gpr_mu mu_;
+    std::mutex do_not_use_sth_;
+#ifdef GPR_HAS_PTHREAD_H
+    pthread_mutex_t do_not_use_pth_;
+#endif
+  };
+};
+
+// MutexLock is a std::
+class MutexLock {
+ public:
+  explicit MutexLock(Mutex* mu) : mu_(mu->get()) {
+    g_core_codegen_interface->gpr_mu_lock(mu_);
+  }
+  explicit MutexLock(gpr_mu* mu) : mu_(mu) {
+    g_core_codegen_interface->gpr_mu_lock(mu_);
+  }
+  ~MutexLock() { g_core_codegen_interface->gpr_mu_unlock(mu_); }
+
+  MutexLock(const MutexLock&) = delete;
+  MutexLock& operator=(const MutexLock&) = delete;
+
+ private:
+  gpr_mu* const mu_;
+};
+
+class ReleasableMutexLock {
+ public:
+  explicit ReleasableMutexLock(Mutex* mu) : mu_(mu->get()) {
+    g_core_codegen_interface->gpr_mu_lock(mu_);
+  }
+  explicit ReleasableMutexLock(gpr_mu* mu) : mu_(mu) {
+    g_core_codegen_interface->gpr_mu_lock(mu_);
+  }
+  ~ReleasableMutexLock() {
+    if (!released_) g_core_codegen_interface->gpr_mu_unlock(mu_);
+  }
+
+  ReleasableMutexLock(const ReleasableMutexLock&) = delete;
+  ReleasableMutexLock& operator=(const ReleasableMutexLock&) = delete;
+
+  void Lock() {
+    GPR_DEBUG_ASSERT(released_);
+    g_core_codegen_interface->gpr_mu_lock(mu_);
+    released_ = false;
+  }
+
+  void Unlock() {
+    GPR_DEBUG_ASSERT(!released_);
+    released_ = true;
+    g_core_codegen_interface->gpr_mu_unlock(mu_);
+  }
+
+ private:
+  gpr_mu* const mu_;
+  bool released_ = false;
+};
+
+class CondVar {
+ public:
+  CondVar() { g_core_codegen_interface->gpr_cv_init(&cv_); }
+  ~CondVar() { g_core_codegen_interface->gpr_cv_destroy(&cv_); }
+
+  CondVar(const CondVar&) = delete;
+  CondVar& operator=(const CondVar&) = delete;
+
+  void Signal() { g_core_codegen_interface->gpr_cv_signal(&cv_); }
+  void Broadcast() { g_core_codegen_interface->gpr_cv_broadcast(&cv_); }
+
+  int Wait(Mutex* mu) {
+    return Wait(mu,
+                g_core_codegen_interface->gpr_inf_future(GPR_CLOCK_REALTIME));
+  }
+  int Wait(Mutex* mu, const gpr_timespec& deadline) {
+    return g_core_codegen_interface->gpr_cv_wait(&cv_, mu->get(), deadline);
+  }
+
+  template <typename Predicate>
+  void WaitUntil(Mutex* mu, Predicate pred) {
+    while (!pred()) {
+      Wait(mu, g_core_codegen_interface->gpr_inf_future(GPR_CLOCK_REALTIME));
+    }
+  }
+
+ private:
+  gpr_cv cv_;
+};
+
+}  // namespace internal
+}  // namespace grpc
+
+#endif  // GRPCPP_IMPL_CODEGEN_SYNC_H
diff --git a/include/grpcpp/impl/codegen/sync_stream.h b/include/grpcpp/impl/codegen/sync_stream.h
index d9edad4..0d3fdfc 100644
--- a/include/grpcpp/impl/codegen/sync_stream.h
+++ b/include/grpcpp/impl/codegen/sync_stream.h
@@ -180,7 +180,7 @@
   ///
   //  Side effect:
   ///   Once complete, the initial metadata read from
-  ///   the server will be accessable through the \a ClientContext used to
+  ///   the server will be accessible through the \a ClientContext used to
   ///   construct this object.
   void WaitForInitialMetadata() override {
     GPR_CODEGEN_ASSERT(!context_->initial_metadata_received_);
@@ -298,7 +298,7 @@
   ///
   //  Side effect:
   ///   Once complete, the initial metadata read from the server will be
-  ///   accessable through the \a ClientContext used to construct this object.
+  ///   accessible through the \a ClientContext used to construct this object.
   void WaitForInitialMetadata() {
     GPR_CODEGEN_ASSERT(!context_->initial_metadata_received_);
 
@@ -449,7 +449,7 @@
   /// with or after the \a Finish method.
   ///
   /// Once complete, the initial metadata read from the server will be
-  /// accessable through the \a ClientContext used to construct this object.
+  /// accessible through the \a ClientContext used to construct this object.
   void WaitForInitialMetadata() override {
     GPR_CODEGEN_ASSERT(!context_->initial_metadata_received_);
 
diff --git a/include/grpcpp/impl/server_builder_plugin.h b/include/grpcpp/impl/server_builder_plugin.h
index 39450b4..84a88f2 100644
--- a/include/grpcpp/impl/server_builder_plugin.h
+++ b/include/grpcpp/impl/server_builder_plugin.h
@@ -23,11 +23,13 @@
 
 #include <grpcpp/support/config.h>
 
-namespace grpc {
+namespace grpc_impl {
 
+class ChannelArguments;
 class ServerBuilder;
 class ServerInitializer;
-class ChannelArguments;
+}  // namespace grpc_impl
+namespace grpc {
 
 /// This interface is meant for internal usage only. Implementations of this
 /// interface should add themselves to a \a ServerBuilder instance through the
@@ -40,14 +42,14 @@
   /// UpdateServerBuilder will be called at an early stage in
   /// ServerBuilder::BuildAndStart(), right after the ServerBuilderOptions have
   /// done their updates.
-  virtual void UpdateServerBuilder(ServerBuilder* builder) {}
+  virtual void UpdateServerBuilder(grpc_impl::ServerBuilder* builder) {}
 
   /// InitServer will be called in ServerBuilder::BuildAndStart(), after the
   /// Server instance is created.
-  virtual void InitServer(ServerInitializer* si) = 0;
+  virtual void InitServer(grpc_impl::ServerInitializer* si) = 0;
 
   /// Finish will be called at the end of ServerBuilder::BuildAndStart().
-  virtual void Finish(ServerInitializer* si) = 0;
+  virtual void Finish(grpc_impl::ServerInitializer* si) = 0;
 
   /// ChangeArguments is an interface that can be used in
   /// ServerBuilderOption::UpdatePlugins
@@ -55,7 +57,7 @@
 
   /// UpdateChannelArguments will be called in ServerBuilder::BuildAndStart(),
   /// before the Server instance is created.
-  virtual void UpdateChannelArguments(ChannelArguments* args) {}
+  virtual void UpdateChannelArguments(grpc_impl::ChannelArguments* args) {}
 
   virtual bool has_sync_methods() const { return false; }
   virtual bool has_async_methods() const { return false; }
diff --git a/include/grpcpp/impl/server_initializer.h b/include/grpcpp/impl/server_initializer.h
index f949fab..d40bb98 100644
--- a/include/grpcpp/impl/server_initializer.h
+++ b/include/grpcpp/impl/server_initializer.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2016 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,36 +19,11 @@
 #ifndef GRPCPP_IMPL_SERVER_INITIALIZER_H
 #define GRPCPP_IMPL_SERVER_INITIALIZER_H
 
-#include <memory>
-#include <vector>
-
-#include <grpcpp/server.h>
+#include <grpcpp/impl/server_initializer_impl.h>
 
 namespace grpc {
 
-class Server;
-class Service;
-
-class ServerInitializer {
- public:
-  ServerInitializer(Server* server) : server_(server) {}
-
-  bool RegisterService(std::shared_ptr<Service> service) {
-    if (!server_->RegisterService(nullptr, service.get())) {
-      return false;
-    }
-    default_services_.push_back(service);
-    return true;
-  }
-
-  const std::vector<grpc::string>* GetServiceList() {
-    return &server_->services_;
-  }
-
- private:
-  Server* server_;
-  std::vector<std::shared_ptr<Service> > default_services_;
-};
+typedef ::grpc_impl::ServerInitializer ServerInitializer;
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/impl/server_initializer_impl.h b/include/grpcpp/impl/server_initializer_impl.h
new file mode 100644
index 0000000..0e2c65f
--- /dev/null
+++ b/include/grpcpp/impl/server_initializer_impl.h
@@ -0,0 +1,57 @@
+/*
+ *
+ * Copyright 2016 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 GRPCPP_IMPL_SERVER_INITIALIZER_IMPL_H
+#define GRPCPP_IMPL_SERVER_INITIALIZER_IMPL_H
+
+#include <memory>
+#include <vector>
+
+#include <grpcpp/server.h>
+
+namespace grpc {
+
+class Service;
+}  // namespace grpc
+namespace grpc_impl {
+class Server;
+
+class ServerInitializer {
+ public:
+  ServerInitializer(grpc::Server* server) : server_(server) {}
+
+  bool RegisterService(std::shared_ptr<grpc::Service> service) {
+    if (!server_->RegisterService(nullptr, service.get())) {
+      return false;
+    }
+    default_services_.push_back(service);
+    return true;
+  }
+
+  const std::vector<grpc::string>* GetServiceList() {
+    return &server_->services_;
+  }
+
+ private:
+  grpc::Server* server_;
+  std::vector<std::shared_ptr<grpc::Service> > default_services_;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_IMPL_SERVER_INITIALIZER_IMPL_H
diff --git a/include/grpcpp/resource_quota.h b/include/grpcpp/resource_quota.h
index 50bd1cb..333767b 100644
--- a/include/grpcpp/resource_quota.h
+++ b/include/grpcpp/resource_quota.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2016 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,50 +19,11 @@
 #ifndef GRPCPP_RESOURCE_QUOTA_H
 #define GRPCPP_RESOURCE_QUOTA_H
 
-struct grpc_resource_quota;
-
-#include <grpcpp/impl/codegen/config.h>
-#include <grpcpp/impl/codegen/grpc_library.h>
+#include <grpcpp/resource_quota_impl.h>
 
 namespace grpc {
 
-/// ResourceQuota represents a bound on memory and thread usage by the gRPC
-/// library. A ResourceQuota can be attached to a server (via \a ServerBuilder),
-/// or a client channel (via \a ChannelArguments).
-/// gRPC will attempt to keep memory and threads used by all attached entities
-/// below the ResourceQuota bound.
-class ResourceQuota final : private GrpcLibraryCodegen {
- public:
-  /// \param name - a unique name for this ResourceQuota.
-  explicit ResourceQuota(const grpc::string& name);
-  ResourceQuota();
-  ~ResourceQuota();
-
-  /// Resize this \a ResourceQuota to a new size. If \a new_size is smaller
-  /// than the current size of the pool, memory usage will be monotonically
-  /// decreased until it falls under \a new_size.
-  /// No time bound is given for this to occur however.
-  ResourceQuota& Resize(size_t new_size);
-
-  /// Set the max number of threads that can be allocated from this
-  /// ResourceQuota object.
-  ///
-  /// If the new_max_threads value is smaller than the current value, no new
-  /// threads are allocated until the number of active threads fall below
-  /// new_max_threads. There is no time bound on when this may happen i.e none
-  /// of the current threads are forcefully destroyed and all threads run their
-  /// normal course.
-  ResourceQuota& SetMaxThreads(int new_max_threads);
-
-  grpc_resource_quota* c_resource_quota() const { return impl_; }
-
- private:
-  ResourceQuota(const ResourceQuota& rhs);
-  ResourceQuota& operator=(const ResourceQuota& rhs);
-
-  grpc_resource_quota* const impl_;
-};
-
+typedef ::grpc_impl::ResourceQuota ResourceQuota;
 }  // namespace grpc
 
 #endif  // GRPCPP_RESOURCE_QUOTA_H
diff --git a/include/grpcpp/resource_quota_impl.h b/include/grpcpp/resource_quota_impl.h
new file mode 100644
index 0000000..16c0e35
--- /dev/null
+++ b/include/grpcpp/resource_quota_impl.h
@@ -0,0 +1,68 @@
+/*
+ *
+ * Copyright 2016 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 GRPCPP_RESOURCE_QUOTA_IMPL_H
+#define GRPCPP_RESOURCE_QUOTA_IMPL_H
+
+struct grpc_resource_quota;
+
+#include <grpcpp/impl/codegen/config.h>
+#include <grpcpp/impl/codegen/grpc_library.h>
+
+namespace grpc_impl {
+
+/// ResourceQuota represents a bound on memory and thread usage by the gRPC
+/// library. A ResourceQuota can be attached to a server (via \a ServerBuilder),
+/// or a client channel (via \a ChannelArguments).
+/// gRPC will attempt to keep memory and threads used by all attached entities
+/// below the ResourceQuota bound.
+class ResourceQuota final : private ::grpc::GrpcLibraryCodegen {
+ public:
+  /// \param name - a unique name for this ResourceQuota.
+  explicit ResourceQuota(const grpc::string& name);
+  ResourceQuota();
+  ~ResourceQuota();
+
+  /// Resize this \a ResourceQuota to a new size. If \a new_size is smaller
+  /// than the current size of the pool, memory usage will be monotonically
+  /// decreased until it falls under \a new_size.
+  /// No time bound is given for this to occur however.
+  ResourceQuota& Resize(size_t new_size);
+
+  /// Set the max number of threads that can be allocated from this
+  /// ResourceQuota object.
+  ///
+  /// If the new_max_threads value is smaller than the current value, no new
+  /// threads are allocated until the number of active threads fall below
+  /// new_max_threads. There is no time bound on when this may happen i.e none
+  /// of the current threads are forcefully destroyed and all threads run their
+  /// normal course.
+  ResourceQuota& SetMaxThreads(int new_max_threads);
+
+  grpc_resource_quota* c_resource_quota() const { return impl_; }
+
+ private:
+  ResourceQuota(const ResourceQuota& rhs);
+  ResourceQuota& operator=(const ResourceQuota& rhs);
+
+  grpc_resource_quota* const impl_;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_RESOURCE_QUOTA_IMPL_H
diff --git a/include/grpcpp/security/auth_metadata_processor.h b/include/grpcpp/security/auth_metadata_processor.h
index 30e24c9..1b66b72 100644
--- a/include/grpcpp/security/auth_metadata_processor.h
+++ b/include/grpcpp/security/auth_metadata_processor.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,42 +19,11 @@
 #ifndef GRPCPP_SECURITY_AUTH_METADATA_PROCESSOR_H
 #define GRPCPP_SECURITY_AUTH_METADATA_PROCESSOR_H
 
-#include <map>
-
-#include <grpcpp/security/auth_context.h>
-#include <grpcpp/support/status.h>
-#include <grpcpp/support/string_ref.h>
+#include <grpcpp/security/auth_metadata_processor_impl.h>
 
 namespace grpc {
 
-/// Interface allowing custom server-side authorization based on credentials
-/// encoded in metadata.  Objects of this type can be passed to
-/// \a ServerCredentials::SetAuthMetadataProcessor().
-class AuthMetadataProcessor {
- public:
-  typedef std::multimap<grpc::string_ref, grpc::string_ref> InputMetadata;
-  typedef std::multimap<grpc::string, grpc::string> OutputMetadata;
-
-  virtual ~AuthMetadataProcessor() {}
-
-  /// If this method returns true, the \a Process function will be scheduled in
-  /// a different thread from the one processing the call.
-  virtual bool IsBlocking() const { return true; }
-
-  /// context is read/write: it contains the properties of the channel peer and
-  /// it is the job of the Process method to augment it with properties derived
-  /// from the passed-in auth_metadata.
-  /// consumed_auth_metadata needs to be filled with metadata that has been
-  /// consumed by the processor and will be removed from the call.
-  /// response_metadata is the metadata that will be sent as part of the
-  /// response.
-  /// If the return value is not Status::OK, the rpc call will be aborted with
-  /// the error code and error message sent back to the client.
-  virtual Status Process(const InputMetadata& auth_metadata,
-                         AuthContext* context,
-                         OutputMetadata* consumed_auth_metadata,
-                         OutputMetadata* response_metadata) = 0;
-};
+typedef ::grpc_impl::AuthMetadataProcessor AuthMetadataProcessor;
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/security/auth_metadata_processor_impl.h b/include/grpcpp/security/auth_metadata_processor_impl.h
new file mode 100644
index 0000000..ae45420
--- /dev/null
+++ b/include/grpcpp/security/auth_metadata_processor_impl.h
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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 GRPCPP_SECURITY_AUTH_METADATA_PROCESSOR_IMPL_H
+#define GRPCPP_SECURITY_AUTH_METADATA_PROCESSOR_IMPL_H
+
+#include <map>
+
+#include <grpcpp/security/auth_context.h>
+#include <grpcpp/support/status.h>
+#include <grpcpp/support/string_ref.h>
+
+namespace grpc_impl {
+
+/// Interface allowing custom server-side authorization based on credentials
+/// encoded in metadata.  Objects of this type can be passed to
+/// \a ServerCredentials::SetAuthMetadataProcessor().
+class AuthMetadataProcessor {
+ public:
+  typedef std::multimap<grpc::string_ref, grpc::string_ref> InputMetadata;
+  typedef std::multimap<grpc::string, grpc::string> OutputMetadata;
+
+  virtual ~AuthMetadataProcessor() {}
+
+  /// If this method returns true, the \a Process function will be scheduled in
+  /// a different thread from the one processing the call.
+  virtual bool IsBlocking() const { return true; }
+
+  /// context is read/write: it contains the properties of the channel peer and
+  /// it is the job of the Process method to augment it with properties derived
+  /// from the passed-in auth_metadata.
+  /// consumed_auth_metadata needs to be filled with metadata that has been
+  /// consumed by the processor and will be removed from the call.
+  /// response_metadata is the metadata that will be sent as part of the
+  /// response.
+  /// If the return value is not Status::OK, the rpc call will be aborted with
+  /// the error code and error message sent back to the client.
+  virtual grpc::Status Process(const InputMetadata& auth_metadata,
+                               grpc::AuthContext* context,
+                               OutputMetadata* consumed_auth_metadata,
+                               OutputMetadata* response_metadata) = 0;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_SECURITY_AUTH_METADATA_PROCESSOR_IMPL_H
diff --git a/include/grpcpp/security/credentials.h b/include/grpcpp/security/credentials.h
index dfea390..e924275 100644
--- a/include/grpcpp/security/credentials.h
+++ b/include/grpcpp/security/credentials.h
@@ -19,257 +19,104 @@
 #ifndef GRPCPP_SECURITY_CREDENTIALS_H
 #define GRPCPP_SECURITY_CREDENTIALS_H
 
-#include <map>
-#include <memory>
-#include <vector>
-
-#include <grpc/grpc_security_constants.h>
-#include <grpcpp/impl/codegen/client_interceptor.h>
-#include <grpcpp/impl/codegen/grpc_library.h>
-#include <grpcpp/security/auth_context.h>
-#include <grpcpp/support/status.h>
-#include <grpcpp/support/string_ref.h>
-
-struct grpc_call;
+#include <grpcpp/security/credentials_impl.h>
 
 namespace grpc {
-class ChannelArguments;
-class Channel;
-class SecureChannelCredentials;
-class CallCredentials;
-class SecureCallCredentials;
 
-class ChannelCredentials;
+typedef ::grpc_impl::ChannelCredentials ChannelCredentials;
+typedef ::grpc_impl::CallCredentials CallCredentials;
+typedef ::grpc_impl::SslCredentialsOptions SslCredentialsOptions;
+typedef ::grpc_impl::SecureCallCredentials SecureCallCredentials;
+typedef ::grpc_impl::SecureChannelCredentials SecureChannelCredentials;
 
-namespace experimental {
-std::shared_ptr<Channel> CreateCustomChannelWithInterceptors(
-    const grpc::string& target,
-    const std::shared_ptr<ChannelCredentials>& creds,
-    const ChannelArguments& args,
-    std::vector<
-        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-        interceptor_creators);
-}  // namespace experimental
+static inline std::shared_ptr<grpc_impl::ChannelCredentials>
+GoogleDefaultCredentials() {
+  return ::grpc_impl::GoogleDefaultCredentials();
+}
 
-/// A channel credentials object encapsulates all the state needed by a client
-/// to authenticate with a server for a given channel.
-/// It can make various assertions, e.g., about the client’s identity, role
-/// for all the calls on that channel.
-///
-/// \see https://grpc.io/docs/guides/auth.html
-class ChannelCredentials : private GrpcLibraryCodegen {
- public:
-  ChannelCredentials();
-  ~ChannelCredentials();
+static inline std::shared_ptr<ChannelCredentials> SslCredentials(
+    const SslCredentialsOptions& options) {
+  return ::grpc_impl::SslCredentials(options);
+}
 
- protected:
-  friend std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
-      const std::shared_ptr<ChannelCredentials>& channel_creds,
-      const std::shared_ptr<CallCredentials>& call_creds);
-
-  virtual SecureChannelCredentials* AsSecureCredentials() = 0;
-
- private:
-  friend std::shared_ptr<Channel> CreateCustomChannel(
-      const grpc::string& target,
-      const std::shared_ptr<ChannelCredentials>& creds,
-      const ChannelArguments& args);
-
-  friend std::shared_ptr<Channel>
-  experimental::CreateCustomChannelWithInterceptors(
-      const grpc::string& target,
-      const std::shared_ptr<ChannelCredentials>& creds,
-      const ChannelArguments& args,
-      std::vector<
-          std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-          interceptor_creators);
-
-  virtual std::shared_ptr<Channel> CreateChannel(
-      const grpc::string& target, const ChannelArguments& args) = 0;
-
-  // This function should have been a pure virtual function, but it is
-  // implemented as a virtual function so that it does not break API.
-  virtual std::shared_ptr<Channel> CreateChannelWithInterceptors(
-      const grpc::string& target, const ChannelArguments& args,
-      std::vector<
-          std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-          interceptor_creators) {
-    return nullptr;
-  }
-};
-
-/// A call credentials object encapsulates the state needed by a client to
-/// authenticate with a server for a given call on a channel.
-///
-/// \see https://grpc.io/docs/guides/auth.html
-class CallCredentials : private GrpcLibraryCodegen {
- public:
-  CallCredentials();
-  ~CallCredentials();
-
-  /// Apply this instance's credentials to \a call.
-  virtual bool ApplyToCall(grpc_call* call) = 0;
-
- protected:
-  friend std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
-      const std::shared_ptr<ChannelCredentials>& channel_creds,
-      const std::shared_ptr<CallCredentials>& call_creds);
-
-  friend std::shared_ptr<CallCredentials> CompositeCallCredentials(
-      const std::shared_ptr<CallCredentials>& creds1,
-      const std::shared_ptr<CallCredentials>& creds2);
-
-  virtual SecureCallCredentials* AsSecureCredentials() = 0;
-};
-
-/// Options used to build SslCredentials.
-struct SslCredentialsOptions {
-  /// The buffer containing the PEM encoding of the server root certificates. If
-  /// this parameter is empty, the default roots will be used.  The default
-  /// roots can be overridden using the \a GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
-  /// environment variable pointing to a file on the file system containing the
-  /// roots.
-  grpc::string pem_root_certs;
-
-  /// The buffer containing the PEM encoding of the client's private key. This
-  /// parameter can be empty if the client does not have a private key.
-  grpc::string pem_private_key;
-
-  /// The buffer containing the PEM encoding of the client's certificate chain.
-  /// This parameter can be empty if the client does not have a certificate
-  /// chain.
-  grpc::string pem_cert_chain;
-};
-
-// Factories for building different types of Credentials The functions may
-// return empty shared_ptr when credentials cannot be created. If a
-// Credentials pointer is returned, it can still be invalid when used to create
-// a channel. A lame channel will be created then and all rpcs will fail on it.
-
-/// Builds credentials with reasonable defaults.
-///
-/// \warning Only use these credentials when connecting to a Google endpoint.
-/// Using these credentials to connect to any other service may result in this
-/// service being able to impersonate your client for requests to Google
-/// services.
-std::shared_ptr<ChannelCredentials> GoogleDefaultCredentials();
-
-/// Builds SSL Credentials given SSL specific options
-std::shared_ptr<ChannelCredentials> SslCredentials(
-    const SslCredentialsOptions& options);
-
-/// Builds credentials for use when running in GCE
-///
-/// \warning Only use these credentials when connecting to a Google endpoint.
-/// Using these credentials to connect to any other service may result in this
-/// service being able to impersonate your client for requests to Google
-/// services.
-std::shared_ptr<CallCredentials> GoogleComputeEngineCredentials();
+static inline std::shared_ptr<grpc_impl::CallCredentials>
+GoogleComputeEngineCredentials() {
+  return ::grpc_impl::GoogleComputeEngineCredentials();
+}
 
 /// Constant for maximum auth token lifetime.
-constexpr long kMaxAuthTokenLifetimeSecs = 3600;
+constexpr long kMaxAuthTokenLifetimeSecs =
+    ::grpc_impl::kMaxAuthTokenLifetimeSecs;
 
-/// Builds Service Account JWT Access credentials.
-/// json_key is the JSON key string containing the client's private key.
-/// token_lifetime_seconds is the lifetime in seconds of each Json Web Token
-/// (JWT) created with this credentials. It should not exceed
-/// \a kMaxAuthTokenLifetimeSecs or will be cropped to this value.
-std::shared_ptr<CallCredentials> ServiceAccountJWTAccessCredentials(
+static inline std::shared_ptr<grpc_impl::CallCredentials>
+ServiceAccountJWTAccessCredentials(
     const grpc::string& json_key,
-    long token_lifetime_seconds = kMaxAuthTokenLifetimeSecs);
+    long token_lifetime_seconds = grpc::kMaxAuthTokenLifetimeSecs) {
+  return ::grpc_impl::ServiceAccountJWTAccessCredentials(
+      json_key, token_lifetime_seconds);
+}
 
-/// Builds refresh token credentials.
-/// json_refresh_token is the JSON string containing the refresh token along
-/// with a client_id and client_secret.
-///
-/// \warning Only use these credentials when connecting to a Google endpoint.
-/// Using these credentials to connect to any other service may result in this
-/// service being able to impersonate your client for requests to Google
-/// services.
-std::shared_ptr<CallCredentials> GoogleRefreshTokenCredentials(
-    const grpc::string& json_refresh_token);
+static inline std::shared_ptr<grpc_impl::CallCredentials>
+GoogleRefreshTokenCredentials(const grpc::string& json_refresh_token) {
+  return ::grpc_impl::GoogleRefreshTokenCredentials(json_refresh_token);
+}
 
-/// Builds access token credentials.
-/// access_token is an oauth2 access token that was fetched using an out of band
-/// mechanism.
-///
-/// \warning Only use these credentials when connecting to a Google endpoint.
-/// Using these credentials to connect to any other service may result in this
-/// service being able to impersonate your client for requests to Google
-/// services.
-std::shared_ptr<CallCredentials> AccessTokenCredentials(
-    const grpc::string& access_token);
+static inline std::shared_ptr<grpc_impl::CallCredentials>
+AccessTokenCredentials(const grpc::string& access_token) {
+  return ::grpc_impl::AccessTokenCredentials(access_token);
+}
 
-/// Builds IAM credentials.
-///
-/// \warning Only use these credentials when connecting to a Google endpoint.
-/// Using these credentials to connect to any other service may result in this
-/// service being able to impersonate your client for requests to Google
-/// services.
-std::shared_ptr<CallCredentials> GoogleIAMCredentials(
+static inline std::shared_ptr<grpc_impl::CallCredentials> GoogleIAMCredentials(
     const grpc::string& authorization_token,
-    const grpc::string& authority_selector);
+    const grpc::string& authority_selector) {
+  return ::grpc_impl::GoogleIAMCredentials(authorization_token,
+                                           authority_selector);
+}
 
-/// Combines a channel credentials and a call credentials into a composite
-/// channel credentials.
-std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
+static inline std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
     const std::shared_ptr<ChannelCredentials>& channel_creds,
-    const std::shared_ptr<CallCredentials>& call_creds);
+    const std::shared_ptr<CallCredentials>& call_creds) {
+  return ::grpc_impl::CompositeChannelCredentials(channel_creds, call_creds);
+}
 
-/// Combines two call credentials objects into a composite call credentials.
-std::shared_ptr<CallCredentials> CompositeCallCredentials(
-    const std::shared_ptr<CallCredentials>& creds1,
-    const std::shared_ptr<CallCredentials>& creds2);
+static inline std::shared_ptr<grpc_impl::CallCredentials>
+CompositeCallCredentials(const std::shared_ptr<CallCredentials>& creds1,
+                         const std::shared_ptr<CallCredentials>& creds2) {
+  return ::grpc_impl::CompositeCallCredentials(creds1, creds2);
+}
 
-/// Credentials for an unencrypted, unauthenticated channel
-std::shared_ptr<ChannelCredentials> InsecureChannelCredentials();
+static inline std::shared_ptr<grpc_impl::ChannelCredentials>
+InsecureChannelCredentials() {
+  return ::grpc_impl::InsecureChannelCredentials();
+}
 
-/// Credentials for a channel using Cronet.
-std::shared_ptr<ChannelCredentials> CronetChannelCredentials(void* engine);
+static inline std::shared_ptr<grpc_impl::ChannelCredentials>
+CronetChannelCredentials(void* engine) {
+  return ::grpc_impl::CronetChannelCredentials(engine);
+}
 
-/// User defined metadata credentials.
-class MetadataCredentialsPlugin {
- public:
-  virtual ~MetadataCredentialsPlugin() {}
+typedef ::grpc_impl::MetadataCredentialsPlugin MetadataCredentialsPlugin;
 
-  /// If this method returns true, the Process function will be scheduled in
-  /// a different thread from the one processing the call.
-  virtual bool IsBlocking() const { return true; }
-
-  /// Type of credentials this plugin is implementing.
-  virtual const char* GetType() const { return ""; }
-
-  /// Gets the auth metatada produced by this plugin.
-  /// The fully qualified method name is:
-  /// service_url + "/" + method_name.
-  /// The channel_auth_context contains (among other things), the identity of
-  /// the server.
-  virtual Status GetMetadata(
-      grpc::string_ref service_url, grpc::string_ref method_name,
-      const AuthContext& channel_auth_context,
-      std::multimap<grpc::string, grpc::string>* metadata) = 0;
-};
-
-std::shared_ptr<CallCredentials> MetadataCredentialsFromPlugin(
-    std::unique_ptr<MetadataCredentialsPlugin> plugin);
+static inline std::shared_ptr<grpc_impl::CallCredentials>
+MetadataCredentialsFromPlugin(
+    std::unique_ptr<MetadataCredentialsPlugin> plugin) {
+  return ::grpc_impl::MetadataCredentialsFromPlugin(std::move(plugin));
+}
 
 namespace experimental {
 
-/// Options used to build AltsCredentials.
-struct AltsCredentialsOptions {
-  /// service accounts of target endpoint that will be acceptable
-  /// by the client. If service accounts are provided and none of them matches
-  /// that of the server, authentication will fail.
-  std::vector<grpc::string> target_service_accounts;
-};
+typedef ::grpc_impl::experimental::AltsCredentialsOptions
+    AltsCredentialsOptions;
 
-/// Builds ALTS Credentials given ALTS specific options
-std::shared_ptr<ChannelCredentials> AltsCredentials(
-    const AltsCredentialsOptions& options);
+static inline std::shared_ptr<grpc_impl::ChannelCredentials> AltsCredentials(
+    const AltsCredentialsOptions& options) {
+  return ::grpc_impl::experimental::AltsCredentials(options);
+}
 
-/// Builds Local Credentials.
-std::shared_ptr<ChannelCredentials> LocalCredentials(
-    grpc_local_connect_type type);
+static inline std::shared_ptr<grpc_impl::ChannelCredentials> LocalCredentials(
+    grpc_local_connect_type type) {
+  return ::grpc_impl::experimental::LocalCredentials(type);
+}
 
 }  // namespace experimental
 }  // namespace grpc
diff --git a/include/grpcpp/security/credentials_impl.h b/include/grpcpp/security/credentials_impl.h
new file mode 100644
index 0000000..29ba2075
--- /dev/null
+++ b/include/grpcpp/security/credentials_impl.h
@@ -0,0 +1,281 @@
+/*
+ *
+ * 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 GRPCPP_SECURITY_CREDENTIALS_IMPL_H
+#define GRPCPP_SECURITY_CREDENTIALS_IMPL_H
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <grpc/grpc_security_constants.h>
+#include <grpcpp/channel.h>
+#include <grpcpp/impl/codegen/client_interceptor.h>
+#include <grpcpp/impl/codegen/grpc_library.h>
+#include <grpcpp/security/auth_context.h>
+#include <grpcpp/support/channel_arguments.h>
+#include <grpcpp/support/status.h>
+#include <grpcpp/support/string_ref.h>
+
+struct grpc_call;
+
+namespace grpc_impl {
+
+class ChannelCredentials;
+class CallCredentials;
+class SecureCallCredentials;
+class SecureChannelCredentials;
+
+std::shared_ptr<::grpc::Channel> CreateCustomChannelImpl(
+    const grpc::string& target,
+    const std::shared_ptr<ChannelCredentials>& creds,
+    const grpc::ChannelArguments& args);
+
+namespace experimental {
+std::shared_ptr<::grpc::Channel> CreateCustomChannelWithInterceptors(
+    const grpc::string& target,
+    const std::shared_ptr<ChannelCredentials>& creds,
+    const grpc::ChannelArguments& args,
+    std::vector<
+        std::unique_ptr<grpc::experimental::ClientInterceptorFactoryInterface>>
+        interceptor_creators);
+}
+
+/// A channel credentials object encapsulates all the state needed by a client
+/// to authenticate with a server for a given channel.
+/// It can make various assertions, e.g., about the client’s identity, role
+/// for all the calls on that channel.
+///
+/// \see https://grpc.io/docs/guides/auth.html
+class ChannelCredentials : private grpc::GrpcLibraryCodegen {
+ public:
+  ChannelCredentials();
+  ~ChannelCredentials();
+
+ protected:
+  friend std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
+      const std::shared_ptr<ChannelCredentials>& channel_creds,
+      const std::shared_ptr<CallCredentials>& call_creds);
+
+  virtual SecureChannelCredentials* AsSecureCredentials() = 0;
+
+ private:
+  friend std::shared_ptr<::grpc::Channel> CreateCustomChannelImpl(
+      const grpc::string& target,
+      const std::shared_ptr<ChannelCredentials>& creds,
+      const grpc::ChannelArguments& args);
+
+  friend std::shared_ptr<::grpc::Channel>
+  grpc_impl::experimental::CreateCustomChannelWithInterceptors(
+      const grpc::string& target,
+      const std::shared_ptr<ChannelCredentials>& creds,
+      const grpc::ChannelArguments& args,
+      std::vector<std::unique_ptr<
+          grpc::experimental::ClientInterceptorFactoryInterface>>
+          interceptor_creators);
+
+  virtual std::shared_ptr<::grpc::Channel> CreateChannelImpl(
+      const grpc::string& target, const grpc::ChannelArguments& args) = 0;
+
+  // This function should have been a pure virtual function, but it is
+  // implemented as a virtual function so that it does not break API.
+  virtual std::shared_ptr<::grpc::Channel> CreateChannelWithInterceptors(
+      const grpc::string& target, const grpc::ChannelArguments& args,
+      std::vector<std::unique_ptr<
+          grpc::experimental::ClientInterceptorFactoryInterface>>
+          interceptor_creators) {
+    return nullptr;
+  }
+};
+
+/// A call credentials object encapsulates the state needed by a client to
+/// authenticate with a server for a given call on a channel.
+///
+/// \see https://grpc.io/docs/guides/auth.html
+class CallCredentials : private grpc::GrpcLibraryCodegen {
+ public:
+  CallCredentials();
+  ~CallCredentials();
+
+  /// Apply this instance's credentials to \a call.
+  virtual bool ApplyToCall(grpc_call* call) = 0;
+
+ protected:
+  friend std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
+      const std::shared_ptr<ChannelCredentials>& channel_creds,
+      const std::shared_ptr<CallCredentials>& call_creds);
+
+  friend std::shared_ptr<CallCredentials> CompositeCallCredentials(
+      const std::shared_ptr<CallCredentials>& creds1,
+      const std::shared_ptr<CallCredentials>& creds2);
+
+  virtual SecureCallCredentials* AsSecureCredentials() = 0;
+};
+
+/// Options used to build SslCredentials.
+struct SslCredentialsOptions {
+  /// The buffer containing the PEM encoding of the server root certificates. If
+  /// this parameter is empty, the default roots will be used.  The default
+  /// roots can be overridden using the \a GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
+  /// environment variable pointing to a file on the file system containing the
+  /// roots.
+  grpc::string pem_root_certs;
+
+  /// The buffer containing the PEM encoding of the client's private key. This
+  /// parameter can be empty if the client does not have a private key.
+  grpc::string pem_private_key;
+
+  /// The buffer containing the PEM encoding of the client's certificate chain.
+  /// This parameter can be empty if the client does not have a certificate
+  /// chain.
+  grpc::string pem_cert_chain;
+};
+
+// Factories for building different types of Credentials The functions may
+// return empty shared_ptr when credentials cannot be created. If a
+// Credentials pointer is returned, it can still be invalid when used to create
+// a channel. A lame channel will be created then and all rpcs will fail on it.
+
+/// Builds credentials with reasonable defaults.
+///
+/// \warning Only use these credentials when connecting to a Google endpoint.
+/// Using these credentials to connect to any other service may result in this
+/// service being able to impersonate your client for requests to Google
+/// services.
+std::shared_ptr<ChannelCredentials> GoogleDefaultCredentials();
+
+/// Builds SSL Credentials given SSL specific options
+std::shared_ptr<ChannelCredentials> SslCredentials(
+    const SslCredentialsOptions& options);
+
+/// Builds credentials for use when running in GCE
+///
+/// \warning Only use these credentials when connecting to a Google endpoint.
+/// Using these credentials to connect to any other service may result in this
+/// service being able to impersonate your client for requests to Google
+/// services.
+std::shared_ptr<CallCredentials> GoogleComputeEngineCredentials();
+
+constexpr long kMaxAuthTokenLifetimeSecs = 3600;
+
+/// Builds Service Account JWT Access credentials.
+/// json_key is the JSON key string containing the client's private key.
+/// token_lifetime_seconds is the lifetime in seconds of each Json Web Token
+/// (JWT) created with this credentials. It should not exceed
+/// \a kMaxAuthTokenLifetimeSecs or will be cropped to this value.
+std::shared_ptr<CallCredentials> ServiceAccountJWTAccessCredentials(
+    const grpc::string& json_key,
+    long token_lifetime_seconds = grpc_impl::kMaxAuthTokenLifetimeSecs);
+
+/// Builds refresh token credentials.
+/// json_refresh_token is the JSON string containing the refresh token along
+/// with a client_id and client_secret.
+///
+/// \warning Only use these credentials when connecting to a Google endpoint.
+/// Using these credentials to connect to any other service may result in this
+/// service being able to impersonate your client for requests to Google
+/// services.
+std::shared_ptr<CallCredentials> GoogleRefreshTokenCredentials(
+    const grpc::string& json_refresh_token);
+
+/// Builds access token credentials.
+/// access_token is an oauth2 access token that was fetched using an out of band
+/// mechanism.
+///
+/// \warning Only use these credentials when connecting to a Google endpoint.
+/// Using these credentials to connect to any other service may result in this
+/// service being able to impersonate your client for requests to Google
+/// services.
+std::shared_ptr<CallCredentials> AccessTokenCredentials(
+    const grpc::string& access_token);
+
+/// Builds IAM credentials.
+///
+/// \warning Only use these credentials when connecting to a Google endpoint.
+/// Using these credentials to connect to any other service may result in this
+/// service being able to impersonate your client for requests to Google
+/// services.
+std::shared_ptr<CallCredentials> GoogleIAMCredentials(
+    const grpc::string& authorization_token,
+    const grpc::string& authority_selector);
+
+/// Combines a channel credentials and a call credentials into a composite
+/// channel credentials.
+std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
+    const std::shared_ptr<ChannelCredentials>& channel_creds,
+    const std::shared_ptr<CallCredentials>& call_creds);
+
+/// Combines two call credentials objects into a composite call credentials.
+std::shared_ptr<CallCredentials> CompositeCallCredentials(
+    const std::shared_ptr<CallCredentials>& creds1,
+    const std::shared_ptr<CallCredentials>& creds2);
+
+/// Credentials for an unencrypted, unauthenticated channel
+std::shared_ptr<ChannelCredentials> InsecureChannelCredentials();
+
+/// Credentials for a channel using Cronet.
+std::shared_ptr<ChannelCredentials> CronetChannelCredentials(void* engine);
+
+/// User defined metadata credentials.
+class MetadataCredentialsPlugin {
+ public:
+  virtual ~MetadataCredentialsPlugin() {}
+
+  /// If this method returns true, the Process function will be scheduled in
+  /// a different thread from the one processing the call.
+  virtual bool IsBlocking() const { return true; }
+
+  /// Type of credentials this plugin is implementing.
+  virtual const char* GetType() const { return ""; }
+
+  /// Gets the auth metatada produced by this plugin.
+  /// The fully qualified method name is:
+  /// service_url + "/" + method_name.
+  /// The channel_auth_context contains (among other things), the identity of
+  /// the server.
+  virtual grpc::Status GetMetadata(
+      grpc::string_ref service_url, grpc::string_ref method_name,
+      const grpc::AuthContext& channel_auth_context,
+      std::multimap<grpc::string, grpc::string>* metadata) = 0;
+};
+
+std::shared_ptr<CallCredentials> MetadataCredentialsFromPlugin(
+    std::unique_ptr<MetadataCredentialsPlugin> plugin);
+
+namespace experimental {
+
+/// Options used to build AltsCredentials.
+struct AltsCredentialsOptions {
+  /// service accounts of target endpoint that will be acceptable
+  /// by the client. If service accounts are provided and none of them matches
+  /// that of the server, authentication will fail.
+  std::vector<grpc::string> target_service_accounts;
+};
+
+/// Builds ALTS Credentials given ALTS specific options
+std::shared_ptr<ChannelCredentials> AltsCredentials(
+    const AltsCredentialsOptions& options);
+
+/// Builds Local Credentials.
+std::shared_ptr<ChannelCredentials> LocalCredentials(
+    grpc_local_connect_type type);
+
+}  // namespace experimental
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_SECURITY_CREDENTIALS_IMPL_H
diff --git a/include/grpcpp/security/server_credentials.h b/include/grpcpp/security/server_credentials.h
index bd00a0a..57f7338 100644
--- a/include/grpcpp/security/server_credentials.h
+++ b/include/grpcpp/security/server_credentials.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,39 +19,15 @@
 #ifndef GRPCPP_SECURITY_SERVER_CREDENTIALS_H
 #define GRPCPP_SECURITY_SERVER_CREDENTIALS_H
 
-#include <memory>
-#include <vector>
+#include <grpcpp/security/server_credentials_impl.h>
 
-#include <grpc/grpc_security_constants.h>
-#include <grpcpp/security/auth_metadata_processor.h>
-#include <grpcpp/support/config.h>
+namespace grpc_impl {
 
-struct grpc_server;
-
-namespace grpc {
 class Server;
+}  // namespace grpc_impl
+namespace grpc {
 
-/// Wrapper around \a grpc_server_credentials, a way to authenticate a server.
-class ServerCredentials {
- public:
-  virtual ~ServerCredentials();
-
-  /// This method is not thread-safe and has to be called before the server is
-  /// started. The last call to this function wins.
-  virtual void SetAuthMetadataProcessor(
-      const std::shared_ptr<AuthMetadataProcessor>& processor) = 0;
-
- private:
-  friend class ::grpc::Server;
-
-  /// Tries to bind \a server to the given \a addr (eg, localhost:1234,
-  /// 192.168.1.1:31416, [::1]:27182, etc.)
-  ///
-  /// \return bound port number on sucess, 0 on failure.
-  // TODO(dgq): the "port" part seems to be a misnomer.
-  virtual int AddPortToServer(const grpc::string& addr,
-                              grpc_server* server) = 0;
-};
+typedef ::grpc_impl::ServerCredentials ServerCredentials;
 
 /// Options to create ServerCredentials with SSL
 struct SslServerCredentialsOptions {
@@ -79,27 +55,29 @@
   grpc_ssl_client_certificate_request_type client_certificate_request;
 };
 
-/// Builds SSL ServerCredentials given SSL specific options
-std::shared_ptr<ServerCredentials> SslServerCredentials(
-    const SslServerCredentialsOptions& options);
+static inline std::shared_ptr<ServerCredentials> SslServerCredentials(
+    const SslServerCredentialsOptions& options) {
+  return ::grpc_impl::SslServerCredentials(options);
+}
 
-/// Builds insecure server credentials.
-std::shared_ptr<ServerCredentials> InsecureServerCredentials();
+static inline std::shared_ptr<ServerCredentials> InsecureServerCredentials() {
+  return ::grpc_impl::InsecureServerCredentials();
+}
 
 namespace experimental {
 
-/// Options to create ServerCredentials with ALTS
-struct AltsServerCredentialsOptions {
-  /// Add fields if needed.
-};
+typedef ::grpc_impl::experimental::AltsServerCredentialsOptions
+    AltsServerCredentialsOptions;
 
-/// Builds ALTS ServerCredentials given ALTS specific options
-std::shared_ptr<ServerCredentials> AltsServerCredentials(
-    const AltsServerCredentialsOptions& options);
+static inline std::shared_ptr<ServerCredentials> AltsServerCredentials(
+    const AltsServerCredentialsOptions& options) {
+  return ::grpc_impl::experimental::AltsServerCredentials(options);
+}
 
-/// Builds Local ServerCredentials.
-std::shared_ptr<ServerCredentials> LocalServerCredentials(
-    grpc_local_connect_type type);
+static inline std::shared_ptr<ServerCredentials> LocalServerCredentials(
+    grpc_local_connect_type type) {
+  return ::grpc_impl::experimental::LocalServerCredentials(type);
+}
 
 }  // namespace experimental
 }  // namespace grpc
diff --git a/include/grpcpp/security/server_credentials_impl.h b/include/grpcpp/security/server_credentials_impl.h
new file mode 100644
index 0000000..f088490
--- /dev/null
+++ b/include/grpcpp/security/server_credentials_impl.h
@@ -0,0 +1,85 @@
+/*
+ *
+ * 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 GRPCPP_SECURITY_SERVER_CREDENTIALS_IMPL_H
+#define GRPCPP_SECURITY_SERVER_CREDENTIALS_IMPL_H
+
+#include <memory>
+#include <vector>
+
+#include <grpc/grpc_security_constants.h>
+#include <grpcpp/security/auth_metadata_processor.h>
+#include <grpcpp/support/config.h>
+
+struct grpc_server;
+
+namespace grpc {
+
+struct SslServerCredentialsOptions;
+}  // namespace grpc
+namespace grpc_impl {
+class Server;
+
+/// Wrapper around \a grpc_server_credentials, a way to authenticate a server.
+class ServerCredentials {
+ public:
+  virtual ~ServerCredentials();
+
+  /// This method is not thread-safe and has to be called before the server is
+  /// started. The last call to this function wins.
+  virtual void SetAuthMetadataProcessor(
+      const std::shared_ptr<grpc::AuthMetadataProcessor>& processor) = 0;
+
+ private:
+  friend class ::grpc_impl::Server;
+
+  /// Tries to bind \a server to the given \a addr (eg, localhost:1234,
+  /// 192.168.1.1:31416, [::1]:27182, etc.)
+  ///
+  /// \return bound port number on success, 0 on failure.
+  // TODO(dgq): the "port" part seems to be a misnomer.
+  virtual int AddPortToServer(const grpc::string& addr,
+                              grpc_server* server) = 0;
+};
+
+/// Builds SSL ServerCredentials given SSL specific options
+std::shared_ptr<ServerCredentials> SslServerCredentials(
+    const grpc::SslServerCredentialsOptions& options);
+
+/// Builds insecure server credentials.
+std::shared_ptr<ServerCredentials> InsecureServerCredentials();
+
+namespace experimental {
+
+/// Options to create ServerCredentials with ALTS
+struct AltsServerCredentialsOptions {
+  /// Add fields if needed.
+};
+
+/// Builds ALTS ServerCredentials given ALTS specific options
+std::shared_ptr<ServerCredentials> AltsServerCredentials(
+    const AltsServerCredentialsOptions& options);
+
+/// Builds Local ServerCredentials.
+std::shared_ptr<ServerCredentials> LocalServerCredentials(
+    grpc_local_connect_type type);
+
+}  // namespace experimental
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_SECURITY_SERVER_CREDENTIALS_IMPL_H
diff --git a/include/grpcpp/server.h b/include/grpcpp/server.h
index f5c99f2..3de2aba 100644
--- a/include/grpcpp/server.h
+++ b/include/grpcpp/server.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,330 +19,11 @@
 #ifndef GRPCPP_SERVER_H
 #define GRPCPP_SERVER_H
 
-#include <condition_variable>
-#include <list>
-#include <memory>
-#include <mutex>
-#include <vector>
-
-#include <grpc/compression.h>
-#include <grpc/support/atm.h>
-#include <grpcpp/completion_queue.h>
-#include <grpcpp/impl/call.h>
-#include <grpcpp/impl/codegen/client_interceptor.h>
-#include <grpcpp/impl/codegen/grpc_library.h>
-#include <grpcpp/impl/codegen/server_interface.h>
-#include <grpcpp/impl/rpc_service_method.h>
-#include <grpcpp/security/server_credentials.h>
-#include <grpcpp/support/channel_arguments.h>
-#include <grpcpp/support/config.h>
-#include <grpcpp/support/status.h>
-
-struct grpc_server;
+#include <grpcpp/server_impl.h>
 
 namespace grpc {
 
-class AsyncGenericService;
-class HealthCheckServiceInterface;
-class ServerContext;
-class ServerInitializer;
-
-/// Represents a gRPC server.
-///
-/// Use a \a grpc::ServerBuilder to create, configure, and start
-/// \a Server instances.
-class Server : public ServerInterface, private GrpcLibraryCodegen {
- public:
-  ~Server();
-
-  /// Block until the server shuts down.
-  ///
-  /// \warning The server must be either shutting down or some other thread must
-  /// call \a Shutdown for this function to ever return.
-  void Wait() override;
-
-  /// Global callbacks are a set of hooks that are called when server
-  /// events occur.  \a SetGlobalCallbacks method is used to register
-  /// the hooks with gRPC.  Note that
-  /// the \a GlobalCallbacks instance will be shared among all
-  /// \a Server instances in an application and can be set exactly
-  /// once per application.
-  class GlobalCallbacks {
-   public:
-    virtual ~GlobalCallbacks() {}
-    /// Called before server is created.
-    virtual void UpdateArguments(ChannelArguments* args) {}
-    /// Called before application callback for each synchronous server request
-    virtual void PreSynchronousRequest(ServerContext* context) = 0;
-    /// Called after application callback for each synchronous server request
-    virtual void PostSynchronousRequest(ServerContext* context) = 0;
-    /// Called before server is started.
-    virtual void PreServerStart(Server* server) {}
-    /// Called after a server port is added.
-    virtual void AddPort(Server* server, const grpc::string& addr,
-                         ServerCredentials* creds, int port) {}
-  };
-  /// Set the global callback object. Can only be called once per application.
-  /// Does not take ownership of callbacks, and expects the pointed to object
-  /// to be alive until all server objects in the process have been destroyed.
-  /// The same \a GlobalCallbacks object will be used throughout the
-  /// application and is shared among all \a Server objects.
-  static void SetGlobalCallbacks(GlobalCallbacks* callbacks);
-
-  /// Returns a \em raw pointer to the underlying \a grpc_server instance.
-  /// EXPERIMENTAL:  for internal/test use only
-  grpc_server* c_server();
-
-  /// Returns the health check service.
-  HealthCheckServiceInterface* GetHealthCheckService() const {
-    return health_check_service_.get();
-  }
-
-  /// Establish a channel for in-process communication
-  std::shared_ptr<Channel> InProcessChannel(const ChannelArguments& args);
-
-  /// NOTE: class experimental_type is not part of the public API of this class.
-  /// TODO(yashykt): Integrate into public API when this is no longer
-  /// experimental.
-  class experimental_type {
-   public:
-    explicit experimental_type(Server* server) : server_(server) {}
-
-    /// Establish a channel for in-process communication with client
-    /// interceptors
-    std::shared_ptr<Channel> InProcessChannelWithInterceptors(
-        const ChannelArguments& args,
-        std::vector<
-            std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-            interceptor_creators);
-
-   private:
-    Server* server_;
-  };
-
-  /// NOTE: The function experimental() is not stable public API. It is a view
-  /// to the experimental components of this class. It may be changed or removed
-  /// at any time.
-  experimental_type experimental() { return experimental_type(this); }
-
- protected:
-  /// Register a service. This call does not take ownership of the service.
-  /// The service must exist for the lifetime of the Server instance.
-  bool RegisterService(const grpc::string* host, Service* service) override;
-
-  /// Try binding the server to the given \a addr endpoint
-  /// (port, and optionally including IP address to bind to).
-  ///
-  /// It can be invoked multiple times. Should be used before
-  /// starting the server.
-  ///
-  /// \param addr The address to try to bind to the server (eg, localhost:1234,
-  /// 192.168.1.1:31416, [::1]:27182, etc.).
-  /// \param creds The credentials associated with the server.
-  ///
-  /// \return bound port number on success, 0 on failure.
-  ///
-  /// \warning It is an error to call this method on an already started server.
-  int AddListeningPort(const grpc::string& addr,
-                       ServerCredentials* creds) override;
-
-  /// NOTE: This is *NOT* a public API. The server constructors are supposed to
-  /// be used by \a ServerBuilder class only. The constructor will be made
-  /// 'private' very soon.
-  ///
-  /// Server constructors. To be used by \a ServerBuilder only.
-  ///
-  /// \param max_message_size Maximum message length that the channel can
-  /// receive.
-  ///
-  /// \param args The channel args
-  ///
-  /// \param sync_server_cqs The completion queues to use if the server is a
-  /// synchronous server (or a hybrid server). The server polls for new RPCs on
-  /// these queues
-  ///
-  /// \param min_pollers The minimum number of polling threads per server
-  /// completion queue (in param sync_server_cqs) to use for listening to
-  /// incoming requests (used only in case of sync server)
-  ///
-  /// \param max_pollers The maximum number of polling threads per server
-  /// completion queue (in param sync_server_cqs) to use for listening to
-  /// incoming requests (used only in case of sync server)
-  ///
-  /// \param sync_cq_timeout_msec The timeout to use when calling AsyncNext() on
-  /// server completion queues passed via sync_server_cqs param.
-  Server(int max_message_size, ChannelArguments* args,
-         std::shared_ptr<std::vector<std::unique_ptr<ServerCompletionQueue>>>
-             sync_server_cqs,
-         int min_pollers, int max_pollers, int sync_cq_timeout_msec,
-         grpc_resource_quota* server_rq = nullptr,
-         std::vector<
-             std::unique_ptr<experimental::ServerInterceptorFactoryInterface>>
-             interceptor_creators = std::vector<std::unique_ptr<
-                 experimental::ServerInterceptorFactoryInterface>>());
-
-  /// Start the server.
-  ///
-  /// \param cqs Completion queues for handling asynchronous services. The
-  /// caller is required to keep all completion queues live until the server is
-  /// destroyed.
-  /// \param num_cqs How many completion queues does \a cqs hold.
-  void Start(ServerCompletionQueue** cqs, size_t num_cqs) override;
-
-  grpc_server* server() override { return server_; }
-
- private:
-  std::vector<std::unique_ptr<experimental::ServerInterceptorFactoryInterface>>*
-  interceptor_creators() override {
-    return &interceptor_creators_;
-  }
-
-  friend class AsyncGenericService;
-  friend class ServerBuilder;
-  friend class ServerInitializer;
-
-  class SyncRequest;
-  class CallbackRequestBase;
-  template <class ServerContextType>
-  class CallbackRequest;
-  class UnimplementedAsyncRequest;
-  class UnimplementedAsyncResponse;
-
-  /// SyncRequestThreadManager is an implementation of ThreadManager. This class
-  /// is responsible for polling for incoming RPCs and calling the RPC handlers.
-  /// This is only used in case of a Sync server (i.e a server exposing a sync
-  /// interface)
-  class SyncRequestThreadManager;
-
-  /// Register a generic service. This call does not take ownership of the
-  /// service. The service must exist for the lifetime of the Server instance.
-  void RegisterAsyncGenericService(AsyncGenericService* service) override;
-
-  /// NOTE: class experimental_registration_type is not part of the public API
-  /// of this class
-  /// TODO(vjpai): Move these contents to the public API of Server when
-  ///              they are no longer experimental
-  class experimental_registration_type final
-      : public experimental_registration_interface {
-   public:
-    explicit experimental_registration_type(Server* server) : server_(server) {}
-    void RegisterCallbackGenericService(
-        experimental::CallbackGenericService* service) override {
-      server_->RegisterCallbackGenericService(service);
-    }
-
-   private:
-    Server* server_;
-  };
-
-  /// TODO(vjpai): Mark this override when experimental type above is deleted
-  void RegisterCallbackGenericService(
-      experimental::CallbackGenericService* service);
-
-  /// NOTE: The function experimental_registration() is not stable public API.
-  /// It is a view to the experimental components of this class. It may be
-  /// changed or removed at any time.
-  experimental_registration_interface* experimental_registration() override {
-    return &experimental_registration_;
-  }
-
-  void PerformOpsOnCall(internal::CallOpSetInterface* ops,
-                        internal::Call* call) override;
-
-  void ShutdownInternal(gpr_timespec deadline) override;
-
-  int max_receive_message_size() const override {
-    return max_receive_message_size_;
-  }
-
-  CompletionQueue* CallbackCQ() override;
-
-  ServerInitializer* initializer();
-
-  // A vector of interceptor factory objects.
-  // This should be destroyed after health_check_service_ and this requirement
-  // is satisfied by declaring interceptor_creators_ before
-  // health_check_service_. (C++ mandates that member objects be destroyed in
-  // the reverse order of initialization.)
-  std::vector<std::unique_ptr<experimental::ServerInterceptorFactoryInterface>>
-      interceptor_creators_;
-
-  const int max_receive_message_size_;
-
-  /// The following completion queues are ONLY used in case of Sync API
-  /// i.e. if the server has any services with sync methods. The server uses
-  /// these completion queues to poll for new RPCs
-  std::shared_ptr<std::vector<std::unique_ptr<ServerCompletionQueue>>>
-      sync_server_cqs_;
-
-  /// List of \a ThreadManager instances (one for each cq in
-  /// the \a sync_server_cqs)
-  std::vector<std::unique_ptr<SyncRequestThreadManager>> sync_req_mgrs_;
-
-  // Outstanding unmatched callback requests, indexed by method.
-  // NOTE: Using a gpr_atm rather than atomic_int because atomic_int isn't
-  //       copyable or movable and thus will cause compilation errors. We
-  //       actually only want to extend the vector before the threaded use
-  //       starts, but this is still a limitation.
-  std::vector<gpr_atm> callback_unmatched_reqs_count_;
-
-  // List of callback requests to start when server actually starts.
-  std::list<CallbackRequestBase*> callback_reqs_to_start_;
-
-  // For registering experimental callback generic service; remove when that
-  // method longer experimental
-  experimental_registration_type experimental_registration_{this};
-
-  // Server status
-  std::mutex mu_;
-  bool started_;
-  bool shutdown_;
-  bool shutdown_notified_;  // Was notify called on the shutdown_cv_
-
-  std::condition_variable shutdown_cv_;
-
-  // It is ok (but not required) to nest callback_reqs_mu_ under mu_ .
-  // Incrementing callback_reqs_outstanding_ is ok without a lock but it must be
-  // decremented under the lock in case it is the last request and enables the
-  // server shutdown. The increment is performance-critical since it happens
-  // during periods of increasing load; the decrement happens only when memory
-  // is maxed out, during server shutdown, or (possibly in a future version)
-  // during decreasing load, so it is less performance-critical.
-  std::mutex callback_reqs_mu_;
-  std::condition_variable callback_reqs_done_cv_;
-  std::atomic_int callback_reqs_outstanding_{0};
-
-  std::shared_ptr<GlobalCallbacks> global_callbacks_;
-
-  std::vector<grpc::string> services_;
-  bool has_async_generic_service_{false};
-  bool has_callback_generic_service_{false};
-
-  // Pointer to the wrapped grpc_server.
-  grpc_server* server_;
-
-  std::unique_ptr<ServerInitializer> server_initializer_;
-
-  std::unique_ptr<HealthCheckServiceInterface> health_check_service_;
-  bool health_check_service_disabled_;
-
-  // When appropriate, use a default callback generic service to handle
-  // unimplemented methods
-  std::unique_ptr<experimental::CallbackGenericService> unimplemented_service_;
-
-  // A special handler for resource exhausted in sync case
-  std::unique_ptr<internal::MethodHandler> resource_exhausted_handler_;
-
-  // Handler for callback generic service, if any
-  std::unique_ptr<internal::MethodHandler> generic_handler_;
-
-  // callback_cq_ references the callbackable completion queue associated
-  // with this server (if any). It is set on the first call to CallbackCQ().
-  // It is _not owned_ by the server; ownership belongs with its internal
-  // shutdown callback tag (invoked when the CQ is fully shutdown).
-  // It is protected by mu_
-  CompletionQueue* callback_cq_ = nullptr;
-};
+typedef ::grpc_impl::Server Server;
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/server_builder.h b/include/grpcpp/server_builder.h
index 18cfbb2..d9ec7c4 100644
--- a/include/grpcpp/server_builder.h
+++ b/include/grpcpp/server_builder.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015-2016 gRPC authors.
+ * Copyright 2019 gRPC authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,323 +19,11 @@
 #ifndef GRPCPP_SERVER_BUILDER_H
 #define GRPCPP_SERVER_BUILDER_H
 
-#include <climits>
-#include <map>
-#include <memory>
-#include <vector>
-
-#include <grpc/compression.h>
-#include <grpc/support/cpu.h>
-#include <grpc/support/workaround_list.h>
-#include <grpcpp/impl/channel_argument_option.h>
-#include <grpcpp/impl/codegen/server_interceptor.h>
-#include <grpcpp/impl/server_builder_option.h>
-#include <grpcpp/impl/server_builder_plugin.h>
-#include <grpcpp/support/config.h>
-
-struct grpc_resource_quota;
+#include <grpcpp/server_builder_impl.h>
 
 namespace grpc {
 
-class AsyncGenericService;
-class ResourceQuota;
-class CompletionQueue;
-class Server;
-class ServerCompletionQueue;
-class ServerCredentials;
-class Service;
-
-namespace testing {
-class ServerBuilderPluginTest;
-}  // namespace testing
-
-namespace experimental {
-class CallbackGenericService;
-}  // namespace experimental
-
-/// A builder class for the creation and startup of \a grpc::Server instances.
-class ServerBuilder {
- public:
-  ServerBuilder();
-  virtual ~ServerBuilder();
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Primary API's
-
-  /// Return a running server which is ready for processing calls.
-  /// Before calling, one typically needs to ensure that:
-  ///  1. a service is registered - so that the server knows what to serve
-  ///     (via RegisterService, or RegisterAsyncGenericService)
-  ///  2. a listening port has been added - so the server knows where to receive
-  ///     traffic (via AddListeningPort)
-  ///  3. [for async api only] completion queues have been added via
-  ///     AddCompletionQueue
-  virtual std::unique_ptr<Server> BuildAndStart();
-
-  /// Register a service. This call does not take ownership of the service.
-  /// The service must exist for the lifetime of the \a Server instance returned
-  /// by \a BuildAndStart().
-  /// Matches requests with any :authority
-  ServerBuilder& RegisterService(Service* service);
-
-  /// Enlists an endpoint \a addr (port with an optional IP address) to
-  /// bind the \a grpc::Server object to be created to.
-  ///
-  /// It can be invoked multiple times.
-  ///
-  /// \param addr_uri The address to try to bind to the server in URI form. If
-  /// the scheme name is omitted, "dns:///" is assumed. To bind to any address,
-  /// please use IPv6 any, i.e., [::]:<port>, which also accepts IPv4
-  /// connections.  Valid values include dns:///localhost:1234, /
-  /// 192.168.1.1:31416, dns:///[::1]:27182, etc.).
-  /// \param creds The credentials associated with the server.
-  /// \param selected_port[out] If not `nullptr`, gets populated with the port
-  /// number bound to the \a grpc::Server for the corresponding endpoint after
-  /// it is successfully bound by BuildAndStart(), 0 otherwise. AddListeningPort
-  /// does not modify this pointer.
-  ServerBuilder& AddListeningPort(const grpc::string& addr_uri,
-                                  std::shared_ptr<ServerCredentials> creds,
-                                  int* selected_port = nullptr);
-
-  /// Add a completion queue for handling asynchronous services.
-  ///
-  /// Best performance is typically obtained by using one thread per polling
-  /// completion queue.
-  ///
-  /// Caller is required to shutdown the server prior to shutting down the
-  /// returned completion queue. Caller is also required to drain the
-  /// completion queue after shutting it down. A typical usage scenario:
-  ///
-  /// // While building the server:
-  /// ServerBuilder builder;
-  /// ...
-  /// cq_ = builder.AddCompletionQueue();
-  /// server_ = builder.BuildAndStart();
-  ///
-  /// // While shutting down the server;
-  /// server_->Shutdown();
-  /// cq_->Shutdown();  // Always *after* the associated server's Shutdown()!
-  /// // Drain the cq_ that was created
-  /// void* ignored_tag;
-  /// bool ignored_ok;
-  /// while (cq_->Next(&ignored_tag, &ignored_ok)) { }
-  ///
-  /// \param is_frequently_polled This is an optional parameter to inform gRPC
-  /// library about whether this completion queue would be frequently polled
-  /// (i.e. by calling \a Next() or \a AsyncNext()). The default value is
-  /// 'true' and is the recommended setting. Setting this to 'false' (i.e.
-  /// not polling the completion queue frequently) will have a significantly
-  /// negative performance impact and hence should not be used in production
-  /// use cases.
-  std::unique_ptr<ServerCompletionQueue> AddCompletionQueue(
-      bool is_frequently_polled = true);
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Less commonly used RegisterService variants
-
-  /// Register a service. This call does not take ownership of the service.
-  /// The service must exist for the lifetime of the \a Server instance returned
-  /// by \a BuildAndStart().
-  /// Only matches requests with :authority \a host
-  ServerBuilder& RegisterService(const grpc::string& host, Service* service);
-
-  /// Register a generic service.
-  /// Matches requests with any :authority
-  /// This is mostly useful for writing generic gRPC Proxies where the exact
-  /// serialization format is unknown
-  ServerBuilder& RegisterAsyncGenericService(AsyncGenericService* service);
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Fine control knobs
-
-  /// Set max receive message size in bytes.
-  /// The default is GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH.
-  ServerBuilder& SetMaxReceiveMessageSize(int max_receive_message_size) {
-    max_receive_message_size_ = max_receive_message_size;
-    return *this;
-  }
-
-  /// Set max send message size in bytes.
-  /// The default is GRPC_DEFAULT_MAX_SEND_MESSAGE_LENGTH.
-  ServerBuilder& SetMaxSendMessageSize(int max_send_message_size) {
-    max_send_message_size_ = max_send_message_size;
-    return *this;
-  }
-
-  /// \deprecated For backward compatibility.
-  ServerBuilder& SetMaxMessageSize(int max_message_size) {
-    return SetMaxReceiveMessageSize(max_message_size);
-  }
-
-  /// Set the support status for compression algorithms. All algorithms are
-  /// enabled by default.
-  ///
-  /// Incoming calls compressed with an unsupported algorithm will fail with
-  /// \a GRPC_STATUS_UNIMPLEMENTED.
-  ServerBuilder& SetCompressionAlgorithmSupportStatus(
-      grpc_compression_algorithm algorithm, bool enabled);
-
-  /// The default compression level to use for all channel calls in the
-  /// absence of a call-specific level.
-  ServerBuilder& SetDefaultCompressionLevel(grpc_compression_level level);
-
-  /// The default compression algorithm to use for all channel calls in the
-  /// absence of a call-specific level. Note that it overrides any compression
-  /// level set by \a SetDefaultCompressionLevel.
-  ServerBuilder& SetDefaultCompressionAlgorithm(
-      grpc_compression_algorithm algorithm);
-
-  /// Set the attached buffer pool for this server
-  ServerBuilder& SetResourceQuota(const ResourceQuota& resource_quota);
-
-  ServerBuilder& SetOption(std::unique_ptr<ServerBuilderOption> option);
-
-  /// Options for synchronous servers.
-  enum SyncServerOption {
-    NUM_CQS,         ///< Number of completion queues.
-    MIN_POLLERS,     ///< Minimum number of polling threads.
-    MAX_POLLERS,     ///< Maximum number of polling threads.
-    CQ_TIMEOUT_MSEC  ///< Completion queue timeout in milliseconds.
-  };
-
-  /// Only useful if this is a Synchronous server.
-  ServerBuilder& SetSyncServerOption(SyncServerOption option, int value);
-
-  /// Add a channel argument (an escape hatch to tuning core library parameters
-  /// directly)
-  template <class T>
-  ServerBuilder& AddChannelArgument(const grpc::string& arg, const T& value) {
-    return SetOption(MakeChannelArgumentOption(arg, value));
-  }
-
-  /// For internal use only: Register a ServerBuilderPlugin factory function.
-  static void InternalAddPluginFactory(
-      std::unique_ptr<ServerBuilderPlugin> (*CreatePlugin)());
-
-  /// Enable a server workaround. Do not use unless you know what the workaround
-  /// does. For explanation and detailed descriptions of workarounds, see
-  /// doc/workarounds.md.
-  ServerBuilder& EnableWorkaround(grpc_workaround_list id);
-
-  /// NOTE: class experimental_type is not part of the public API of this class.
-  /// TODO(yashykt): Integrate into public API when this is no longer
-  /// experimental.
-  class experimental_type {
-   public:
-    explicit experimental_type(ServerBuilder* builder) : builder_(builder) {}
-
-    void SetInterceptorCreators(
-        std::vector<
-            std::unique_ptr<experimental::ServerInterceptorFactoryInterface>>
-            interceptor_creators) {
-      builder_->interceptor_creators_ = std::move(interceptor_creators);
-    }
-
-    /// Register a generic service that uses the callback API.
-    /// Matches requests with any :authority
-    /// This is mostly useful for writing generic gRPC Proxies where the exact
-    /// serialization format is unknown
-    ServerBuilder& RegisterCallbackGenericService(
-        experimental::CallbackGenericService* service);
-
-   private:
-    ServerBuilder* builder_;
-  };
-
-  /// NOTE: The function experimental() is not stable public API. It is a view
-  /// to the experimental components of this class. It may be changed or removed
-  /// at any time.
-  experimental_type experimental() { return experimental_type(this); }
-
- protected:
-  /// Experimental, to be deprecated
-  struct Port {
-    grpc::string addr;
-    std::shared_ptr<ServerCredentials> creds;
-    int* selected_port;
-  };
-
-  /// Experimental, to be deprecated
-  typedef std::unique_ptr<grpc::string> HostString;
-  struct NamedService {
-    explicit NamedService(Service* s) : service(s) {}
-    NamedService(const grpc::string& h, Service* s)
-        : host(new grpc::string(h)), service(s) {}
-    HostString host;
-    Service* service;
-  };
-
-  /// Experimental, to be deprecated
-  std::vector<Port> ports() { return ports_; }
-
-  /// Experimental, to be deprecated
-  std::vector<NamedService*> services() {
-    std::vector<NamedService*> service_refs;
-    for (auto& ptr : services_) {
-      service_refs.push_back(ptr.get());
-    }
-    return service_refs;
-  }
-
-  /// Experimental, to be deprecated
-  std::vector<ServerBuilderOption*> options() {
-    std::vector<ServerBuilderOption*> option_refs;
-    for (auto& ptr : options_) {
-      option_refs.push_back(ptr.get());
-    }
-    return option_refs;
-  }
-
- private:
-  friend class ::grpc::testing::ServerBuilderPluginTest;
-
-  struct SyncServerSettings {
-    SyncServerSettings()
-        : num_cqs(1), min_pollers(1), max_pollers(2), cq_timeout_msec(10000) {}
-
-    /// Number of server completion queues to create to listen to incoming RPCs.
-    int num_cqs;
-
-    /// Minimum number of threads per completion queue that should be listening
-    /// to incoming RPCs.
-    int min_pollers;
-
-    /// Maximum number of threads per completion queue that can be listening to
-    /// incoming RPCs.
-    int max_pollers;
-
-    /// The timeout for server completion queue's AsyncNext call.
-    int cq_timeout_msec;
-  };
-
-  int max_receive_message_size_;
-  int max_send_message_size_;
-  std::vector<std::unique_ptr<ServerBuilderOption>> options_;
-  std::vector<std::unique_ptr<NamedService>> services_;
-  std::vector<Port> ports_;
-
-  SyncServerSettings sync_server_settings_;
-
-  /// List of completion queues added via \a AddCompletionQueue method.
-  std::vector<ServerCompletionQueue*> cqs_;
-
-  std::shared_ptr<ServerCredentials> creds_;
-  std::vector<std::unique_ptr<ServerBuilderPlugin>> plugins_;
-  grpc_resource_quota* resource_quota_;
-  AsyncGenericService* generic_service_{nullptr};
-  experimental::CallbackGenericService* callback_generic_service_{nullptr};
-  struct {
-    bool is_set;
-    grpc_compression_level level;
-  } maybe_default_compression_level_;
-  struct {
-    bool is_set;
-    grpc_compression_algorithm algorithm;
-  } maybe_default_compression_algorithm_;
-  uint32_t enabled_compression_algorithms_bitset_;
-  std::vector<std::unique_ptr<experimental::ServerInterceptorFactoryInterface>>
-      interceptor_creators_;
-};
+typedef ::grpc_impl::ServerBuilder ServerBuilder;
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/server_builder_impl.h b/include/grpcpp/server_builder_impl.h
new file mode 100644
index 0000000..57f8dc0
--- /dev/null
+++ b/include/grpcpp/server_builder_impl.h
@@ -0,0 +1,355 @@
+/*
+ *
+ * Copyright 2015-2016 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 GRPCPP_SERVER_BUILDER_IMPL_H
+#define GRPCPP_SERVER_BUILDER_IMPL_H
+
+#include <climits>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <grpc/compression.h>
+#include <grpc/support/cpu.h>
+#include <grpc/support/workaround_list.h>
+#include <grpcpp/impl/channel_argument_option.h>
+#include <grpcpp/impl/codegen/server_interceptor.h>
+#include <grpcpp/impl/server_builder_option.h>
+#include <grpcpp/impl/server_builder_plugin.h>
+#include <grpcpp/server.h>
+#include <grpcpp/support/config.h>
+
+struct grpc_resource_quota;
+
+namespace grpc_impl {
+
+class CompletionQueue;
+class ResourceQuota;
+class Server;
+class ServerCompletionQueue;
+class ServerCredentials;
+}  // namespace grpc_impl
+namespace grpc {
+
+class AsyncGenericService;
+class Service;
+
+namespace testing {
+class ServerBuilderPluginTest;
+}  // namespace testing
+
+namespace experimental {
+class CallbackGenericService;
+}
+}  // namespace grpc
+namespace grpc_impl {
+
+/// A builder class for the creation and startup of \a grpc::Server instances.
+class ServerBuilder {
+ public:
+  ServerBuilder();
+  virtual ~ServerBuilder();
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Primary API's
+
+  /// Return a running server which is ready for processing calls.
+  /// Before calling, one typically needs to ensure that:
+  ///  1. a service is registered - so that the server knows what to serve
+  ///     (via RegisterService, or RegisterAsyncGenericService)
+  ///  2. a listening port has been added - so the server knows where to receive
+  ///     traffic (via AddListeningPort)
+  ///  3. [for async api only] completion queues have been added via
+  ///     AddCompletionQueue
+  virtual std::unique_ptr<grpc::Server> BuildAndStart();
+
+  /// Register a service. This call does not take ownership of the service.
+  /// The service must exist for the lifetime of the \a Server instance returned
+  /// by \a BuildAndStart().
+  /// Matches requests with any :authority
+  ServerBuilder& RegisterService(grpc::Service* service);
+
+  /// Enlists an endpoint \a addr (port with an optional IP address) to
+  /// bind the \a grpc::Server object to be created to.
+  ///
+  /// It can be invoked multiple times.
+  ///
+  /// \param addr_uri The address to try to bind to the server in URI form. If
+  /// the scheme name is omitted, "dns:///" is assumed. To bind to any address,
+  /// please use IPv6 any, i.e., [::]:<port>, which also accepts IPv4
+  /// connections.  Valid values include dns:///localhost:1234, /
+  /// 192.168.1.1:31416, dns:///[::1]:27182, etc.).
+  /// \param creds The credentials associated with the server.
+  /// \param selected_port[out] If not `nullptr`, gets populated with the port
+  /// number bound to the \a grpc::Server for the corresponding endpoint after
+  /// it is successfully bound by BuildAndStart(), 0 otherwise. AddListeningPort
+  /// does not modify this pointer.
+  ServerBuilder& AddListeningPort(
+      const grpc::string& addr_uri,
+      std::shared_ptr<grpc_impl::ServerCredentials> creds,
+      int* selected_port = nullptr);
+
+  /// Add a completion queue for handling asynchronous services.
+  ///
+  /// Best performance is typically obtained by using one thread per polling
+  /// completion queue.
+  ///
+  /// Caller is required to shutdown the server prior to shutting down the
+  /// returned completion queue. Caller is also required to drain the
+  /// completion queue after shutting it down. A typical usage scenario:
+  ///
+  /// // While building the server:
+  /// ServerBuilder builder;
+  /// ...
+  /// cq_ = builder.AddCompletionQueue();
+  /// server_ = builder.BuildAndStart();
+  ///
+  /// // While shutting down the server;
+  /// server_->Shutdown();
+  /// cq_->Shutdown();  // Always *after* the associated server's Shutdown()!
+  /// // Drain the cq_ that was created
+  /// void* ignored_tag;
+  /// bool ignored_ok;
+  /// while (cq_->Next(&ignored_tag, &ignored_ok)) { }
+  ///
+  /// \param is_frequently_polled This is an optional parameter to inform gRPC
+  /// library about whether this completion queue would be frequently polled
+  /// (i.e. by calling \a Next() or \a AsyncNext()). The default value is
+  /// 'true' and is the recommended setting. Setting this to 'false' (i.e.
+  /// not polling the completion queue frequently) will have a significantly
+  /// negative performance impact and hence should not be used in production
+  /// use cases.
+  std::unique_ptr<grpc_impl::ServerCompletionQueue> AddCompletionQueue(
+      bool is_frequently_polled = true);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Less commonly used RegisterService variants
+
+  /// Register a service. This call does not take ownership of the service.
+  /// The service must exist for the lifetime of the \a Server instance
+  /// returned by \a BuildAndStart(). Only matches requests with :authority \a
+  /// host
+  ServerBuilder& RegisterService(const grpc::string& host,
+                                 grpc::Service* service);
+
+  /// Register a generic service.
+  /// Matches requests with any :authority
+  /// This is mostly useful for writing generic gRPC Proxies where the exact
+  /// serialization format is unknown
+  ServerBuilder& RegisterAsyncGenericService(
+      grpc::AsyncGenericService* service);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Fine control knobs
+
+  /// Set max receive message size in bytes.
+  /// The default is GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH.
+  ServerBuilder& SetMaxReceiveMessageSize(int max_receive_message_size) {
+    max_receive_message_size_ = max_receive_message_size;
+    return *this;
+  }
+
+  /// Set max send message size in bytes.
+  /// The default is GRPC_DEFAULT_MAX_SEND_MESSAGE_LENGTH.
+  ServerBuilder& SetMaxSendMessageSize(int max_send_message_size) {
+    max_send_message_size_ = max_send_message_size;
+    return *this;
+  }
+
+  /// \deprecated For backward compatibility.
+  ServerBuilder& SetMaxMessageSize(int max_message_size) {
+    return SetMaxReceiveMessageSize(max_message_size);
+  }
+
+  /// Set the support status for compression algorithms. All algorithms are
+  /// enabled by default.
+  ///
+  /// Incoming calls compressed with an unsupported algorithm will fail with
+  /// \a GRPC_STATUS_UNIMPLEMENTED.
+  ServerBuilder& SetCompressionAlgorithmSupportStatus(
+      grpc_compression_algorithm algorithm, bool enabled);
+
+  /// The default compression level to use for all channel calls in the
+  /// absence of a call-specific level.
+  ServerBuilder& SetDefaultCompressionLevel(grpc_compression_level level);
+
+  /// The default compression algorithm to use for all channel calls in the
+  /// absence of a call-specific level. Note that it overrides any compression
+  /// level set by \a SetDefaultCompressionLevel.
+  ServerBuilder& SetDefaultCompressionAlgorithm(
+      grpc_compression_algorithm algorithm);
+
+  /// Set the attached buffer pool for this server
+  ServerBuilder& SetResourceQuota(
+      const grpc_impl::ResourceQuota& resource_quota);
+
+  ServerBuilder& SetOption(std::unique_ptr<grpc::ServerBuilderOption> option);
+
+  /// Options for synchronous servers.
+  enum SyncServerOption {
+    NUM_CQS,         ///< Number of completion queues.
+    MIN_POLLERS,     ///< Minimum number of polling threads.
+    MAX_POLLERS,     ///< Maximum number of polling threads.
+    CQ_TIMEOUT_MSEC  ///< Completion queue timeout in milliseconds.
+  };
+
+  /// Only useful if this is a Synchronous server.
+  ServerBuilder& SetSyncServerOption(SyncServerOption option, int value);
+
+  /// Add a channel argument (an escape hatch to tuning core library parameters
+  /// directly)
+  template <class T>
+  ServerBuilder& AddChannelArgument(const grpc::string& arg, const T& value) {
+    return SetOption(grpc::MakeChannelArgumentOption(arg, value));
+  }
+
+  /// For internal use only: Register a ServerBuilderPlugin factory function.
+  static void InternalAddPluginFactory(
+      std::unique_ptr<grpc::ServerBuilderPlugin> (*CreatePlugin)());
+
+  /// Enable a server workaround. Do not use unless you know what the workaround
+  /// does. For explanation and detailed descriptions of workarounds, see
+  /// doc/workarounds.md.
+  ServerBuilder& EnableWorkaround(grpc_workaround_list id);
+
+  /// NOTE: class experimental_type is not part of the public API of this class.
+  /// TODO(yashykt): Integrate into public API when this is no longer
+  /// experimental.
+  class experimental_type {
+   public:
+    explicit experimental_type(grpc_impl::ServerBuilder* builder)
+        : builder_(builder) {}
+
+    void SetInterceptorCreators(
+        std::vector<std::unique_ptr<
+            grpc::experimental::ServerInterceptorFactoryInterface>>
+            interceptor_creators) {
+      builder_->interceptor_creators_ = std::move(interceptor_creators);
+    }
+
+    /// Register a generic service that uses the callback API.
+    /// Matches requests with any :authority
+    /// This is mostly useful for writing generic gRPC Proxies where the exact
+    /// serialization format is unknown
+    ServerBuilder& RegisterCallbackGenericService(
+        grpc::experimental::CallbackGenericService* service);
+
+   private:
+    ServerBuilder* builder_;
+  };
+
+  /// NOTE: The function experimental() is not stable public API. It is a view
+  /// to the experimental components of this class. It may be changed or removed
+  /// at any time.
+  experimental_type experimental() { return experimental_type(this); }
+
+ protected:
+  /// Experimental, to be deprecated
+  struct Port {
+    grpc::string addr;
+    std::shared_ptr<grpc_impl::ServerCredentials> creds;
+    int* selected_port;
+  };
+
+  /// Experimental, to be deprecated
+  typedef std::unique_ptr<grpc::string> HostString;
+  struct NamedService {
+    explicit NamedService(grpc::Service* s) : service(s) {}
+    NamedService(const grpc::string& h, grpc::Service* s)
+        : host(new grpc::string(h)), service(s) {}
+    HostString host;
+    grpc::Service* service;
+  };
+
+  /// Experimental, to be deprecated
+  std::vector<Port> ports() { return ports_; }
+
+  /// Experimental, to be deprecated
+  std::vector<NamedService*> services() {
+    std::vector<NamedService*> service_refs;
+    for (auto& ptr : services_) {
+      service_refs.push_back(ptr.get());
+    }
+    return service_refs;
+  }
+
+  /// Experimental, to be deprecated
+  std::vector<grpc::ServerBuilderOption*> options() {
+    std::vector<grpc::ServerBuilderOption*> option_refs;
+    for (auto& ptr : options_) {
+      option_refs.push_back(ptr.get());
+    }
+    return option_refs;
+  }
+
+ private:
+  friend class ::grpc::testing::ServerBuilderPluginTest;
+
+  struct SyncServerSettings {
+    SyncServerSettings()
+        : num_cqs(1), min_pollers(1), max_pollers(2), cq_timeout_msec(10000) {}
+
+    /// Number of server completion queues to create to listen to incoming RPCs.
+    int num_cqs;
+
+    /// Minimum number of threads per completion queue that should be listening
+    /// to incoming RPCs.
+    int min_pollers;
+
+    /// Maximum number of threads per completion queue that can be listening to
+    /// incoming RPCs.
+    int max_pollers;
+
+    /// The timeout for server completion queue's AsyncNext call.
+    int cq_timeout_msec;
+  };
+
+  int max_receive_message_size_;
+  int max_send_message_size_;
+  std::vector<std::unique_ptr<grpc::ServerBuilderOption>> options_;
+  std::vector<std::unique_ptr<NamedService>> services_;
+  std::vector<Port> ports_;
+
+  SyncServerSettings sync_server_settings_;
+
+  /// List of completion queues added via \a AddCompletionQueue method.
+  std::vector<grpc_impl::ServerCompletionQueue*> cqs_;
+
+  std::shared_ptr<grpc_impl::ServerCredentials> creds_;
+  std::vector<std::unique_ptr<grpc::ServerBuilderPlugin>> plugins_;
+  grpc_resource_quota* resource_quota_;
+  grpc::AsyncGenericService* generic_service_{nullptr};
+  grpc::experimental::CallbackGenericService* callback_generic_service_{
+      nullptr};
+  struct {
+    bool is_set;
+    grpc_compression_level level;
+  } maybe_default_compression_level_;
+  struct {
+    bool is_set;
+    grpc_compression_algorithm algorithm;
+  } maybe_default_compression_algorithm_;
+  uint32_t enabled_compression_algorithms_bitset_;
+  std::vector<
+      std::unique_ptr<grpc::experimental::ServerInterceptorFactoryInterface>>
+      interceptor_creators_;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_SERVER_BUILDER_IMPL_H
diff --git a/include/grpcpp/server_impl.h b/include/grpcpp/server_impl.h
new file mode 100644
index 0000000..2a53265
--- /dev/null
+++ b/include/grpcpp/server_impl.h
@@ -0,0 +1,361 @@
+/*
+ *
+ * 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 GRPCPP_SERVER_IMPL_H
+#define GRPCPP_SERVER_IMPL_H
+
+#include <condition_variable>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#include <grpc/compression.h>
+#include <grpc/support/atm.h>
+#include <grpcpp/channel.h>
+#include <grpcpp/completion_queue.h>
+#include <grpcpp/health_check_service_interface.h>
+#include <grpcpp/impl/call.h>
+#include <grpcpp/impl/codegen/client_interceptor.h>
+#include <grpcpp/impl/codegen/grpc_library.h>
+#include <grpcpp/impl/codegen/server_interface.h>
+#include <grpcpp/impl/rpc_service_method.h>
+#include <grpcpp/security/server_credentials.h>
+#include <grpcpp/support/channel_arguments.h>
+#include <grpcpp/support/config.h>
+#include <grpcpp/support/status.h>
+
+struct grpc_server;
+
+namespace grpc {
+
+class AsyncGenericService;
+class ServerContext;
+
+}  // namespace grpc
+
+namespace grpc_impl {
+
+class ServerInitializer;
+
+/// Represents a gRPC server.
+///
+/// Use a \a grpc::ServerBuilder to create, configure, and start
+/// \a Server instances.
+class Server : public grpc::ServerInterface, private grpc::GrpcLibraryCodegen {
+ public:
+  ~Server();
+
+  /// Block until the server shuts down.
+  ///
+  /// \warning The server must be either shutting down or some other thread must
+  /// call \a Shutdown for this function to ever return.
+  void Wait() override;
+
+  /// Global callbacks are a set of hooks that are called when server
+  /// events occur.  \a SetGlobalCallbacks method is used to register
+  /// the hooks with gRPC.  Note that
+  /// the \a GlobalCallbacks instance will be shared among all
+  /// \a Server instances in an application and can be set exactly
+  /// once per application.
+  class GlobalCallbacks {
+   public:
+    virtual ~GlobalCallbacks() {}
+    /// Called before server is created.
+    virtual void UpdateArguments(grpc::ChannelArguments* args) {}
+    /// Called before application callback for each synchronous server request
+    virtual void PreSynchronousRequest(grpc::ServerContext* context) = 0;
+    /// Called after application callback for each synchronous server request
+    virtual void PostSynchronousRequest(grpc::ServerContext* context) = 0;
+    /// Called before server is started.
+    virtual void PreServerStart(Server* server) {}
+    /// Called after a server port is added.
+    virtual void AddPort(Server* server, const grpc::string& addr,
+                         grpc::ServerCredentials* creds, int port) {}
+  };
+  /// Set the global callback object. Can only be called once per application.
+  /// Does not take ownership of callbacks, and expects the pointed to object
+  /// to be alive until all server objects in the process have been destroyed.
+  /// The same \a GlobalCallbacks object will be used throughout the
+  /// application and is shared among all \a Server objects.
+  static void SetGlobalCallbacks(GlobalCallbacks* callbacks);
+
+  /// Returns a \em raw pointer to the underlying \a grpc_server instance.
+  /// EXPERIMENTAL:  for internal/test use only
+  grpc_server* c_server();
+
+  /// Returns the health check service.
+  grpc::HealthCheckServiceInterface* GetHealthCheckService() const {
+    return health_check_service_.get();
+  }
+
+  /// Establish a channel for in-process communication
+  std::shared_ptr<::grpc::Channel> InProcessChannel(
+      const grpc::ChannelArguments& args);
+
+  /// NOTE: class experimental_type is not part of the public API of this class.
+  /// TODO(yashykt): Integrate into public API when this is no longer
+  /// experimental.
+  class experimental_type {
+   public:
+    explicit experimental_type(Server* server) : server_(server) {}
+
+    /// Establish a channel for in-process communication with client
+    /// interceptors
+    std::shared_ptr<::grpc::Channel> InProcessChannelWithInterceptors(
+        const grpc::ChannelArguments& args,
+        std::vector<std::unique_ptr<
+            grpc::experimental::ClientInterceptorFactoryInterface>>
+            interceptor_creators);
+
+   private:
+    Server* server_;
+  };
+
+  /// NOTE: The function experimental() is not stable public API. It is a view
+  /// to the experimental components of this class. It may be changed or removed
+  /// at any time.
+  experimental_type experimental() { return experimental_type(this); }
+
+ protected:
+  /// Register a service. This call does not take ownership of the service.
+  /// The service must exist for the lifetime of the Server instance.
+  bool RegisterService(const grpc::string* host,
+                       grpc::Service* service) override;
+
+  /// Try binding the server to the given \a addr endpoint
+  /// (port, and optionally including IP address to bind to).
+  ///
+  /// It can be invoked multiple times. Should be used before
+  /// starting the server.
+  ///
+  /// \param addr The address to try to bind to the server (eg, localhost:1234,
+  /// 192.168.1.1:31416, [::1]:27182, etc.).
+  /// \param creds The credentials associated with the server.
+  ///
+  /// \return bound port number on success, 0 on failure.
+  ///
+  /// \warning It is an error to call this method on an already started server.
+  int AddListeningPort(const grpc::string& addr,
+                       grpc::ServerCredentials* creds) override;
+
+  /// NOTE: This is *NOT* a public API. The server constructors are supposed to
+  /// be used by \a ServerBuilder class only. The constructor will be made
+  /// 'private' very soon.
+  ///
+  /// Server constructors. To be used by \a ServerBuilder only.
+  ///
+  /// \param max_message_size Maximum message length that the channel can
+  /// receive.
+  ///
+  /// \param args The channel args
+  ///
+  /// \param sync_server_cqs The completion queues to use if the server is a
+  /// synchronous server (or a hybrid server). The server polls for new RPCs on
+  /// these queues
+  ///
+  /// \param min_pollers The minimum number of polling threads per server
+  /// completion queue (in param sync_server_cqs) to use for listening to
+  /// incoming requests (used only in case of sync server)
+  ///
+  /// \param max_pollers The maximum number of polling threads per server
+  /// completion queue (in param sync_server_cqs) to use for listening to
+  /// incoming requests (used only in case of sync server)
+  ///
+  /// \param sync_cq_timeout_msec The timeout to use when calling AsyncNext() on
+  /// server completion queues passed via sync_server_cqs param.
+  Server(
+      int max_message_size, grpc::ChannelArguments* args,
+      std::shared_ptr<std::vector<std::unique_ptr<grpc::ServerCompletionQueue>>>
+          sync_server_cqs,
+      int min_pollers, int max_pollers, int sync_cq_timeout_msec,
+      grpc_resource_quota* server_rq = nullptr,
+      std::vector<std::unique_ptr<
+          grpc::experimental::ServerInterceptorFactoryInterface>>
+          interceptor_creators = std::vector<std::unique_ptr<
+              grpc::experimental::ServerInterceptorFactoryInterface>>());
+
+  /// Start the server.
+  ///
+  /// \param cqs Completion queues for handling asynchronous services. The
+  /// caller is required to keep all completion queues live until the server is
+  /// destroyed.
+  /// \param num_cqs How many completion queues does \a cqs hold.
+  void Start(grpc::ServerCompletionQueue** cqs, size_t num_cqs) override;
+
+  grpc_server* server() override { return server_; }
+
+ private:
+  std::vector<
+      std::unique_ptr<grpc::experimental::ServerInterceptorFactoryInterface>>*
+  interceptor_creators() override {
+    return &interceptor_creators_;
+  }
+
+  friend class grpc::AsyncGenericService;
+  friend class grpc_impl::ServerBuilder;
+  friend class grpc_impl::ServerInitializer;
+
+  class SyncRequest;
+  class CallbackRequestBase;
+  template <class ServerContextType>
+  class CallbackRequest;
+  class UnimplementedAsyncRequest;
+  class UnimplementedAsyncResponse;
+
+  /// SyncRequestThreadManager is an implementation of ThreadManager. This class
+  /// is responsible for polling for incoming RPCs and calling the RPC handlers.
+  /// This is only used in case of a Sync server (i.e a server exposing a sync
+  /// interface)
+  class SyncRequestThreadManager;
+
+  /// Register a generic service. This call does not take ownership of the
+  /// service. The service must exist for the lifetime of the Server instance.
+  void RegisterAsyncGenericService(grpc::AsyncGenericService* service) override;
+
+  /// NOTE: class experimental_registration_type is not part of the public API
+  /// of this class
+  /// TODO(vjpai): Move these contents to the public API of Server when
+  ///              they are no longer experimental
+  class experimental_registration_type final
+      : public experimental_registration_interface {
+   public:
+    explicit experimental_registration_type(Server* server) : server_(server) {}
+    void RegisterCallbackGenericService(
+        grpc::experimental::CallbackGenericService* service) override {
+      server_->RegisterCallbackGenericService(service);
+    }
+
+   private:
+    Server* server_;
+  };
+
+  /// TODO(vjpai): Mark this override when experimental type above is deleted
+  void RegisterCallbackGenericService(
+      grpc::experimental::CallbackGenericService* service);
+
+  /// NOTE: The function experimental_registration() is not stable public API.
+  /// It is a view to the experimental components of this class. It may be
+  /// changed or removed at any time.
+  experimental_registration_interface* experimental_registration() override {
+    return &experimental_registration_;
+  }
+
+  void PerformOpsOnCall(grpc::internal::CallOpSetInterface* ops,
+                        grpc::internal::Call* call) override;
+
+  void ShutdownInternal(gpr_timespec deadline) override;
+
+  int max_receive_message_size() const override {
+    return max_receive_message_size_;
+  }
+
+  grpc::CompletionQueue* CallbackCQ() override;
+
+  grpc_impl::ServerInitializer* initializer();
+
+  // A vector of interceptor factory objects.
+  // This should be destroyed after health_check_service_ and this requirement
+  // is satisfied by declaring interceptor_creators_ before
+  // health_check_service_. (C++ mandates that member objects be destroyed in
+  // the reverse order of initialization.)
+  std::vector<
+      std::unique_ptr<grpc::experimental::ServerInterceptorFactoryInterface>>
+      interceptor_creators_;
+
+  const int max_receive_message_size_;
+
+  /// The following completion queues are ONLY used in case of Sync API
+  /// i.e. if the server has any services with sync methods. The server uses
+  /// these completion queues to poll for new RPCs
+  std::shared_ptr<std::vector<std::unique_ptr<grpc::ServerCompletionQueue>>>
+      sync_server_cqs_;
+
+  /// List of \a ThreadManager instances (one for each cq in
+  /// the \a sync_server_cqs)
+  std::vector<std::unique_ptr<SyncRequestThreadManager>> sync_req_mgrs_;
+
+  // Outstanding unmatched callback requests, indexed by method.
+  // NOTE: Using a gpr_atm rather than atomic_int because atomic_int isn't
+  //       copyable or movable and thus will cause compilation errors. We
+  //       actually only want to extend the vector before the threaded use
+  //       starts, but this is still a limitation.
+  std::vector<gpr_atm> callback_unmatched_reqs_count_;
+
+  // List of callback requests to start when server actually starts.
+  std::list<CallbackRequestBase*> callback_reqs_to_start_;
+
+  // For registering experimental callback generic service; remove when that
+  // method longer experimental
+  experimental_registration_type experimental_registration_{this};
+
+  // Server status
+  grpc::internal::Mutex mu_;
+  bool started_;
+  bool shutdown_;
+  bool shutdown_notified_;  // Was notify called on the shutdown_cv_
+
+  grpc::internal::CondVar shutdown_cv_;
+
+  // It is ok (but not required) to nest callback_reqs_mu_ under mu_ .
+  // Incrementing callback_reqs_outstanding_ is ok without a lock but it must be
+  // decremented under the lock in case it is the last request and enables the
+  // server shutdown. The increment is performance-critical since it happens
+  // during periods of increasing load; the decrement happens only when memory
+  // is maxed out, during server shutdown, or (possibly in a future version)
+  // during decreasing load, so it is less performance-critical.
+  grpc::internal::Mutex callback_reqs_mu_;
+  grpc::internal::CondVar callback_reqs_done_cv_;
+  std::atomic_int callback_reqs_outstanding_{0};
+
+  std::shared_ptr<GlobalCallbacks> global_callbacks_;
+
+  std::vector<grpc::string> services_;
+  bool has_async_generic_service_{false};
+  bool has_callback_generic_service_{false};
+
+  // Pointer to the wrapped grpc_server.
+  grpc_server* server_;
+
+  std::unique_ptr<grpc_impl::ServerInitializer> server_initializer_;
+
+  std::unique_ptr<grpc::HealthCheckServiceInterface> health_check_service_;
+  bool health_check_service_disabled_;
+
+  // When appropriate, use a default callback generic service to handle
+  // unimplemented methods
+  std::unique_ptr<grpc::experimental::CallbackGenericService>
+      unimplemented_service_;
+
+  // A special handler for resource exhausted in sync case
+  std::unique_ptr<grpc::internal::MethodHandler> resource_exhausted_handler_;
+
+  // Handler for callback generic service, if any
+  std::unique_ptr<grpc::internal::MethodHandler> generic_handler_;
+
+  // callback_cq_ references the callbackable completion queue associated
+  // with this server (if any). It is set on the first call to CallbackCQ().
+  // It is _not owned_ by the server; ownership belongs with its internal
+  // shutdown callback tag (invoked when the CQ is fully shutdown).
+  // It is protected by mu_
+  grpc::CompletionQueue* callback_cq_ = nullptr;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_SERVER_IMPL_H
diff --git a/include/grpcpp/support/channel_arguments.h b/include/grpcpp/support/channel_arguments.h
index 217929d..593aaec 100644
--- a/include/grpcpp/support/channel_arguments.h
+++ b/include/grpcpp/support/channel_arguments.h
@@ -19,129 +19,17 @@
 #ifndef GRPCPP_SUPPORT_CHANNEL_ARGUMENTS_H
 #define GRPCPP_SUPPORT_CHANNEL_ARGUMENTS_H
 
-#include <list>
-#include <vector>
+#include <grpcpp/support/channel_arguments_impl.h>
 
-#include <grpc/compression.h>
-#include <grpc/grpc.h>
-#include <grpcpp/support/config.h>
+namespace grpc_impl {
+
+class SecureChannelCredentials;
+class ResourceQuota;
+}  // namespace grpc_impl
 
 namespace grpc {
-namespace testing {
-class ChannelArgumentsTest;
-}  // namespace testing
 
-class ResourceQuota;
-
-/// Options for channel creation. The user can use generic setters to pass
-/// key value pairs down to C channel creation code. For gRPC related options,
-/// concrete setters are provided.
-class ChannelArguments {
- public:
-  ChannelArguments();
-  ~ChannelArguments();
-
-  ChannelArguments(const ChannelArguments& other);
-  ChannelArguments& operator=(ChannelArguments other) {
-    Swap(other);
-    return *this;
-  }
-
-  void Swap(ChannelArguments& other);
-
-  /// Dump arguments in this instance to \a channel_args. Does not take
-  /// ownership of \a channel_args.
-  ///
-  /// Note that the underlying arguments are shared. Changes made to either \a
-  /// channel_args or this instance would be reflected on both.
-  void SetChannelArgs(grpc_channel_args* channel_args) const;
-
-  // gRPC specific channel argument setters
-  /// Set target name override for SSL host name checking. This option is for
-  /// testing only and should never be used in production.
-  void SetSslTargetNameOverride(const grpc::string& name);
-  // TODO(yangg) add flow control options
-  /// Set the compression algorithm for the channel.
-  void SetCompressionAlgorithm(grpc_compression_algorithm algorithm);
-
-  /// Set the grpclb fallback timeout (in ms) for the channel. If this amount
-  /// of time has passed but we have not gotten any non-empty \a serverlist from
-  /// the balancer, we will fall back to use the backend address(es) returned by
-  /// the resolver.
-  void SetGrpclbFallbackTimeout(int fallback_timeout);
-
-  /// For client channel's, the socket mutator operates on
-  /// "channel" sockets. For server's, the socket mutator operates
-  /// only on "listen" sockets.
-  /// TODO(apolcyn): allow socket mutators to also operate
-  /// on server "channel" sockets, and adjust the socket mutator
-  /// object to be more speficic about which type of socket
-  /// it should operate on.
-  void SetSocketMutator(grpc_socket_mutator* mutator);
-
-  /// Set the string to prepend to the user agent.
-  void SetUserAgentPrefix(const grpc::string& user_agent_prefix);
-
-  /// Set the buffer pool to be attached to the constructed channel.
-  void SetResourceQuota(const ResourceQuota& resource_quota);
-
-  /// Set the max receive and send message sizes.
-  void SetMaxReceiveMessageSize(int size);
-  void SetMaxSendMessageSize(int size);
-
-  /// Set LB policy name.
-  /// Note that if the name resolver returns only balancer addresses, the
-  /// grpclb LB policy will be used, regardless of what is specified here.
-  void SetLoadBalancingPolicyName(const grpc::string& lb_policy_name);
-
-  /// Set service config in JSON form.
-  /// Primarily meant for use in unit tests.
-  void SetServiceConfigJSON(const grpc::string& service_config_json);
-
-  // Generic channel argument setters. Only for advanced use cases.
-  /// Set an integer argument \a value under \a key.
-  void SetInt(const grpc::string& key, int value);
-
-  // Generic channel argument setter. Only for advanced use cases.
-  /// Set a pointer argument \a value under \a key. Owership is not transferred.
-  void SetPointer(const grpc::string& key, void* value);
-
-  void SetPointerWithVtable(const grpc::string& key, void* value,
-                            const grpc_arg_pointer_vtable* vtable);
-
-  /// Set a textual argument \a value under \a key.
-  void SetString(const grpc::string& key, const grpc::string& value);
-
-  /// Return (by value) a C \a grpc_channel_args structure which points to
-  /// arguments owned by this \a ChannelArguments instance
-  grpc_channel_args c_channel_args() const {
-    grpc_channel_args out;
-    out.num_args = args_.size();
-    out.args = args_.empty() ? NULL : const_cast<grpc_arg*>(&args_[0]);
-    return out;
-  }
-
- private:
-  friend class SecureChannelCredentials;
-  friend class testing::ChannelArgumentsTest;
-
-  /// Default pointer argument operations.
-  struct PointerVtableMembers {
-    static void* Copy(void* in) { return in; }
-    static void Destroy(void* in) {}
-    static int Compare(void* a, void* b) {
-      if (a < b) return -1;
-      if (a > b) return 1;
-      return 0;
-    }
-  };
-
-  // Returns empty string when it is not set.
-  grpc::string GetSslTargetNameOverride() const;
-
-  std::vector<grpc_arg> args_;
-  std::list<grpc::string> strings_;
-};
+typedef ::grpc_impl::ChannelArguments ChannelArguments;
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/support/channel_arguments_impl.h b/include/grpcpp/support/channel_arguments_impl.h
new file mode 100644
index 0000000..0efeadc
--- /dev/null
+++ b/include/grpcpp/support/channel_arguments_impl.h
@@ -0,0 +1,152 @@
+/*
+ *
+ * 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 GRPCPP_SUPPORT_CHANNEL_ARGUMENTS_IMPL_H
+#define GRPCPP_SUPPORT_CHANNEL_ARGUMENTS_IMPL_H
+
+#include <list>
+#include <vector>
+
+#include <grpc/compression.h>
+#include <grpc/grpc.h>
+#include <grpcpp/resource_quota.h>
+#include <grpcpp/support/config.h>
+
+namespace grpc {
+namespace testing {
+class ChannelArgumentsTest;
+}  // namespace testing
+}  // namespace grpc
+
+namespace grpc_impl {
+
+class SecureChannelCredentials;
+
+/// Options for channel creation. The user can use generic setters to pass
+/// key value pairs down to C channel creation code. For gRPC related options,
+/// concrete setters are provided.
+class ChannelArguments {
+ public:
+  ChannelArguments();
+  ~ChannelArguments();
+
+  ChannelArguments(const ChannelArguments& other);
+  ChannelArguments& operator=(ChannelArguments other) {
+    Swap(other);
+    return *this;
+  }
+
+  void Swap(ChannelArguments& other);
+
+  /// Dump arguments in this instance to \a channel_args. Does not take
+  /// ownership of \a channel_args.
+  ///
+  /// Note that the underlying arguments are shared. Changes made to either \a
+  /// channel_args or this instance would be reflected on both.
+  void SetChannelArgs(grpc_channel_args* channel_args) const;
+
+  // gRPC specific channel argument setters
+  /// Set target name override for SSL host name checking. This option is for
+  /// testing only and should never be used in production.
+  void SetSslTargetNameOverride(const grpc::string& name);
+  // TODO(yangg) add flow control options
+  /// Set the compression algorithm for the channel.
+  void SetCompressionAlgorithm(grpc_compression_algorithm algorithm);
+
+  /// Set the grpclb fallback timeout (in ms) for the channel. If this amount
+  /// of time has passed but we have not gotten any non-empty \a serverlist from
+  /// the balancer, we will fall back to use the backend address(es) returned by
+  /// the resolver.
+  void SetGrpclbFallbackTimeout(int fallback_timeout);
+
+  /// For client channel's, the socket mutator operates on
+  /// "channel" sockets. For server's, the socket mutator operates
+  /// only on "listen" sockets.
+  /// TODO(apolcyn): allow socket mutators to also operate
+  /// on server "channel" sockets, and adjust the socket mutator
+  /// object to be more speficic about which type of socket
+  /// it should operate on.
+  void SetSocketMutator(grpc_socket_mutator* mutator);
+
+  /// Set the string to prepend to the user agent.
+  void SetUserAgentPrefix(const grpc::string& user_agent_prefix);
+
+  /// Set the buffer pool to be attached to the constructed channel.
+  void SetResourceQuota(const grpc::ResourceQuota& resource_quota);
+
+  /// Set the max receive and send message sizes.
+  void SetMaxReceiveMessageSize(int size);
+  void SetMaxSendMessageSize(int size);
+
+  /// Set LB policy name.
+  /// Note that if the name resolver returns only balancer addresses, the
+  /// grpclb LB policy will be used, regardless of what is specified here.
+  void SetLoadBalancingPolicyName(const grpc::string& lb_policy_name);
+
+  /// Set service config in JSON form.
+  /// Primarily meant for use in unit tests.
+  void SetServiceConfigJSON(const grpc::string& service_config_json);
+
+  // Generic channel argument setters. Only for advanced use cases.
+  /// Set an integer argument \a value under \a key.
+  void SetInt(const grpc::string& key, int value);
+
+  // Generic channel argument setter. Only for advanced use cases.
+  /// Set a pointer argument \a value under \a key. Owership is not transferred.
+  void SetPointer(const grpc::string& key, void* value);
+
+  void SetPointerWithVtable(const grpc::string& key, void* value,
+                            const grpc_arg_pointer_vtable* vtable);
+
+  /// Set a textual argument \a value under \a key.
+  void SetString(const grpc::string& key, const grpc::string& value);
+
+  /// Return (by value) a C \a grpc_channel_args structure which points to
+  /// arguments owned by this \a ChannelArguments instance
+  grpc_channel_args c_channel_args() const {
+    grpc_channel_args out;
+    out.num_args = args_.size();
+    out.args = args_.empty() ? NULL : const_cast<grpc_arg*>(&args_[0]);
+    return out;
+  }
+
+ private:
+  friend class grpc_impl::SecureChannelCredentials;
+  friend class grpc::testing::ChannelArgumentsTest;
+
+  /// Default pointer argument operations.
+  struct PointerVtableMembers {
+    static void* Copy(void* in) { return in; }
+    static void Destroy(void* in) {}
+    static int Compare(void* a, void* b) {
+      if (a < b) return -1;
+      if (a > b) return 1;
+      return 0;
+    }
+  };
+
+  // Returns empty string when it is not set.
+  grpc::string GetSslTargetNameOverride() const;
+
+  std::vector<grpc_arg> args_;
+  std::list<grpc::string> strings_;
+};
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_SUPPORT_CHANNEL_ARGUMENTS_IMPL_H
diff --git a/include/grpcpp/support/error_details.h b/include/grpcpp/support/error_details.h
index 84931d9..07bc750 100644
--- a/include/grpcpp/support/error_details.h
+++ b/include/grpcpp/support/error_details.h
@@ -19,7 +19,7 @@
 #ifndef GRPCPP_SUPPORT_ERROR_DETAILS_H
 #define GRPCPP_SUPPORT_ERROR_DETAILS_H
 
-#include <grpcpp/support/status.h>
+#include <grpcpp/support/error_details_impl.h>
 
 namespace google {
 namespace rpc {
@@ -29,17 +29,15 @@
 
 namespace grpc {
 
-/// Map a \a grpc::Status to a \a google::rpc::Status.
-/// The given \a to object will be cleared.
-/// On success, returns status with OK.
-/// Returns status with \a INVALID_ARGUMENT, if failed to deserialize.
-/// Returns status with \a FAILED_PRECONDITION, if \a to is nullptr.
-Status ExtractErrorDetails(const Status& from, ::google::rpc::Status* to);
+static inline Status ExtractErrorDetails(const Status& from,
+                                         ::google::rpc::Status* to) {
+  return ::grpc_impl::ExtractErrorDetails(from, to);
+}
 
-/// Map \a google::rpc::Status to a \a grpc::Status.
-/// Returns OK on success.
-/// Returns status with \a FAILED_PRECONDITION if \a to is nullptr.
-Status SetErrorDetails(const ::google::rpc::Status& from, Status* to);
+static inline Status SetErrorDetails(const ::google::rpc::Status& from,
+                                     Status* to) {
+  return ::grpc_impl::SetErrorDetails(from, to);
+}
 
 }  // namespace grpc
 
diff --git a/include/grpcpp/support/error_details_impl.h b/include/grpcpp/support/error_details_impl.h
new file mode 100644
index 0000000..ae5f04c
--- /dev/null
+++ b/include/grpcpp/support/error_details_impl.h
@@ -0,0 +1,48 @@
+/*
+ *
+ * Copyright 2017 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 GRPCPP_SUPPORT_ERROR_DETAILS_IMPL_H
+#define GRPCPP_SUPPORT_ERROR_DETAILS_IMPL_H
+
+#include <grpcpp/support/status.h>
+
+namespace google {
+namespace rpc {
+class Status;
+}  // namespace rpc
+}  // namespace google
+
+namespace grpc_impl {
+
+/// Map a \a grpc::Status to a \a google::rpc::Status.
+/// The given \a to object will be cleared.
+/// On success, returns status with OK.
+/// Returns status with \a INVALID_ARGUMENT, if failed to deserialize.
+/// Returns status with \a FAILED_PRECONDITION, if \a to is nullptr.
+grpc::Status ExtractErrorDetails(const grpc::Status& from,
+                                 ::google::rpc::Status* to);
+
+/// Map \a google::rpc::Status to a \a grpc::Status.
+/// Returns OK on success.
+/// Returns status with \a FAILED_PRECONDITION if \a to is nullptr.
+grpc::Status SetErrorDetails(const ::google::rpc::Status& from,
+                             grpc::Status* to);
+
+}  // namespace grpc_impl
+
+#endif  // GRPCPP_SUPPORT_ERROR_DETAILS_IMPL_H
diff --git a/include/grpcpp/support/message_allocator.h b/include/grpcpp/support/message_allocator.h
new file mode 100644
index 0000000..20ce072
--- /dev/null
+++ b/include/grpcpp/support/message_allocator.h
@@ -0,0 +1,24 @@
+/*
+ *
+ * Copyright 2019 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 GRPCPP_SUPPORT_MESSAGE_ALLOCATOR_H
+#define GRPCPP_SUPPORT_MESSAGE_ALLOCATOR_H
+
+#include <grpcpp/impl/codegen/message_allocator.h>
+
+#endif  // GRPCPP_SUPPORT_MESSAGE_ALLOCATOR_H
diff --git a/package.xml b/package.xml
index 9167164..04d9f35 100644
--- a/package.xml
+++ b/package.xml
@@ -13,12 +13,12 @@
  <date>2018-01-19</date>
  <time>16:06:07</time>
  <version>
-  <release>1.20.1</release>
-  <api>1.20.1</api>
+  <release>1.21.0dev</release>
+  <api>1.21.0dev</api>
  </version>
  <stability>
-  <release>stable</release>
-  <api>stable</api>
+  <release>beta</release>
+  <api>beta</api>
  </stability>
  <license>Apache 2.0</license>
  <notes>
@@ -104,15 +104,21 @@
     <file baseinstalldir="/" name="src/core/lib/gpr/tmpfile.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/useful.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/abstract.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/arena.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/atomic.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/fork.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/global_config.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/global_config_custom.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/global_config_env.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/global_config_generic.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/manual_constructor.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/map.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/memory.h" role="src" />
-    <file baseinstalldir="/" name="src/core/lib/gprpp/mutex_lock.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/pair.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/sync.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/thd.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/profiling/timers.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/alloc.cc" role="src" />
-    <file baseinstalldir="/" name="src/core/lib/gpr/arena.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/atm.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/cpu_iphone.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/cpu_linux.cc" role="src" />
@@ -145,7 +151,9 @@
     <file baseinstalldir="/" name="src/core/lib/gpr/tmpfile_posix.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/tmpfile_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/wrap_memcpy.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/arena.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/fork.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gprpp/global_config_env.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/thd_posix.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/thd_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/profiling/basic_timers.cc" role="src" />
@@ -330,6 +338,7 @@
     <file baseinstalldir="/" name="src/core/lib/channel/handshaker_registry.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/channel/status_util.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/algorithm_metadata.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/compression/compression_args.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/compression_internal.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/message_compress.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/stream_compression.h" role="src" />
@@ -349,12 +358,15 @@
     <file baseinstalldir="/" name="src/core/lib/iomgr/block_annotate.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/buffer_list.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/call_combiner.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/cfstream_handle.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/closure.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/combiner.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/dynamic_annotations.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint_cfstream.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint_pair.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/error.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/error_cfstream.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/error_internal.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/ev_epoll1_linux.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/ev_epollex_linux.h" role="src" />
@@ -465,6 +477,8 @@
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/lb_policy/subchannel_list.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/max_age/max_age_filter.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/message_size/message_size_filter.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/http/client_authority_filter.h" role="src" />
@@ -484,6 +498,7 @@
     <file baseinstalldir="/" name="src/core/lib/channel/handshaker_registry.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/channel/status_util.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/compression.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/compression/compression_args.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/compression_internal.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/message_compress.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/compression/stream_compression.cc" role="src" />
@@ -496,12 +511,15 @@
     <file baseinstalldir="/" name="src/core/lib/http/parser.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/buffer_list.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/call_combiner.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/cfstream_handle.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/combiner.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint_cfstream.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint_pair_posix.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint_pair_uv.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/endpoint_pair_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/error.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/error_cfstream.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/ev_epoll1_linux.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/ev_epollex_linux.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/ev_poll_posix.cc" role="src" />
@@ -522,6 +540,7 @@
     <file baseinstalldir="/" name="src/core/lib/iomgr/iomgr_custom.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/iomgr_internal.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/iomgr_posix.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/iomgr_posix_cfstream.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/iomgr_uv.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/iomgr_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/is_epollexclusive_available.cc" role="src" />
@@ -550,6 +569,7 @@
     <file baseinstalldir="/" name="src/core/lib/iomgr/socket_utils_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/socket_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/tcp_client.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/tcp_client_cfstream.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/tcp_client_custom.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/tcp_client_posix.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/tcp_client_windows.cc" role="src" />
@@ -783,12 +803,16 @@
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/census/grpc_context.cc" role="src" />
diff --git a/src/android/test/interop/app/src/main/cpp/grpc-interop.cc b/src/android/test/interop/app/src/main/cpp/grpc-interop.cc
index 0783425..b507552 100644
--- a/src/android/test/interop/app/src/main/cpp/grpc-interop.cc
+++ b/src/android/test/interop/app/src/main/cpp/grpc-interop.cc
@@ -18,8 +18,8 @@
 
 #include <grpcpp/grpcpp.h>
 #include <jni.h>
-#include <src/core/lib/gpr/env.h>
 
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/cpp/interop/interop_client.h"
 
 extern "C" JNIEXPORT void JNICALL
@@ -28,7 +28,7 @@
                                                            jstring path_raw) {
   const char* path = env->GetStringUTFChars(path_raw, (jboolean*)0);
 
-  gpr_setenv("GRPC_DEFAULT_SSL_ROOTS_FILE_PATH", path);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, path);
 }
 
 std::shared_ptr<grpc::testing::InteropClient> GetClient(const char* host,
@@ -45,7 +45,7 @@
     credentials = grpc::InsecureChannelCredentials();
   }
 
-  grpc::testing::ChannelCreationFunc channel_creation_func = 
+  grpc::testing::ChannelCreationFunc channel_creation_func =
       std::bind(grpc::CreateChannel, host_port, credentials);
   return std::shared_ptr<grpc::testing::InteropClient>(
       new grpc::testing::InteropClient(channel_creation_func, true, false));
diff --git a/src/compiler/cpp_generator.cc b/src/compiler/cpp_generator.cc
index 96e9ab8..77cd7b5 100644
--- a/src/compiler/cpp_generator.cc
+++ b/src/compiler/cpp_generator.cc
@@ -84,7 +84,7 @@
 }
 
 grpc::string GetHeaderPrologue(grpc_generator::File* file,
-                               const Parameters& /*params*/) {
+                               const Parameters& params) {
   grpc::string output;
   {
     // Scope the output stream so it closes and finalizes output to the string.
@@ -94,7 +94,9 @@
     vars["filename"] = file->filename();
     vars["filename_identifier"] = FilenameIdentifier(file->filename());
     vars["filename_base"] = file->filename_without_ext();
-    vars["message_header_ext"] = kCppGeneratorMessageHeaderExt;
+    vars["message_header_ext"] = params.message_header_extension.empty()
+                                     ? kCppGeneratorMessageHeaderExt
+                                     : params.message_header_extension;
 
     printer->Print(vars, "// Generated by the gRPC C++ plugin.\n");
     printer->Print(vars,
@@ -115,6 +117,13 @@
   return output;
 }
 
+// Convert from "a/b/c.proto" to "#include \"a/b/c$message_header_ext$\"\n"
+grpc::string ImportInludeFromProtoName(const grpc::string& proto_name) {
+  return grpc::string("#include \"") +
+         proto_name.substr(0, proto_name.size() - 6) +
+         grpc::string("$message_header_ext$\"\n");
+}
+
 grpc::string GetHeaderIncludes(grpc_generator::File* file,
                                const Parameters& params) {
   grpc::string output;
@@ -145,13 +154,35 @@
     PrintIncludes(printer.get(), headers, params.use_system_headers,
                   params.grpc_search_path);
     printer->Print(vars, "\n");
-    printer->Print(vars, "namespace grpc {\n");
-    printer->Print(vars, "class CompletionQueue;\n");
+    printer->Print(vars, "namespace grpc_impl {\n");
     printer->Print(vars, "class Channel;\n");
+    printer->Print(vars, "class CompletionQueue;\n");
     printer->Print(vars, "class ServerCompletionQueue;\n");
+    printer->Print(vars, "}  // namespace grpc_impl\n\n");
+    printer->Print(vars, "namespace grpc {\n");
+    printer->Print(vars, "namespace experimental {\n");
+    printer->Print(vars, "template <typename RequestT, typename ResponseT>\n");
+    printer->Print(vars, "class MessageAllocator;\n");
+    printer->Print(vars, "}  // namespace experimental\n");
+    printer->Print(vars, "}  // namespace grpc_impl\n\n");
+    printer->Print(vars, "namespace grpc {\n");
     printer->Print(vars, "class ServerContext;\n");
     printer->Print(vars, "}  // namespace grpc\n\n");
 
+    vars["message_header_ext"] = params.message_header_extension.empty()
+                                     ? kCppGeneratorMessageHeaderExt
+                                     : params.message_header_extension;
+
+    if (params.include_import_headers) {
+      const std::vector<grpc::string> import_names = file->GetImportNames();
+      for (const auto& import_name : import_names) {
+        const grpc::string include_name =
+            ImportInludeFromProtoName(import_name);
+        printer->Print(vars, include_name.c_str());
+      }
+      printer->PrintRaw("\n");
+    }
+
     if (!file->package().empty()) {
       std::vector<grpc::string> parts = file->package_parts();
 
@@ -584,6 +615,14 @@
                    "virtual void $Method$(::grpc::ClientContext* context, "
                    "const ::grpc::ByteBuffer* request, $Response$* response, "
                    "std::function<void(::grpc::Status)>) = 0;\n");
+    printer->Print(*vars,
+                   "virtual void $Method$(::grpc::ClientContext* context, "
+                   "const $Request$* request, $Response$* response, "
+                   "::grpc::experimental::ClientUnaryReactor* reactor) = 0;\n");
+    printer->Print(*vars,
+                   "virtual void $Method$(::grpc::ClientContext* context, "
+                   "const ::grpc::ByteBuffer* request, $Response$* response, "
+                   "::grpc::experimental::ClientUnaryReactor* reactor) = 0;\n");
   } else if (ClientOnlyStreaming(method)) {
     printer->Print(*vars,
                    "virtual void $Method$(::grpc::ClientContext* context, "
@@ -650,6 +689,16 @@
                    "void $Method$(::grpc::ClientContext* context, "
                    "const ::grpc::ByteBuffer* request, $Response$* response, "
                    "std::function<void(::grpc::Status)>) override;\n");
+    printer->Print(
+        *vars,
+        "void $Method$(::grpc::ClientContext* context, "
+        "const $Request$* request, $Response$* response, "
+        "::grpc::experimental::ClientUnaryReactor* reactor) override;\n");
+    printer->Print(
+        *vars,
+        "void $Method$(::grpc::ClientContext* context, "
+        "const ::grpc::ByteBuffer* request, $Response$* response, "
+        "::grpc::experimental::ClientUnaryReactor* reactor) override;\n");
   } else if (ClientOnlyStreaming(method)) {
     printer->Print(*vars,
                    "void $Method$(::grpc::ClientContext* context, "
@@ -970,7 +1019,15 @@
         "controller) {\n"
         "               return this->$"
         "Method$(context, request, response, controller);\n"
-        "             }));\n");
+        "             }));\n}\n");
+    printer->Print(*vars,
+                   "void SetMessageAllocatorFor_$Method$(\n"
+                   "    ::grpc::experimental::MessageAllocator< "
+                   "$RealRequest$, $RealResponse$>* allocator) {\n"
+                   "  static_cast<::grpc::internal::CallbackUnaryHandler< "
+                   "$RealRequest$, $RealResponse$>*>(\n"
+                   "      ::grpc::Service::experimental().GetHandler($Idx$))\n"
+                   "          ->SetMessageAllocator(allocator);\n");
   } else if (ClientOnlyStreaming(method)) {
     printer->Print(
         *vars,
@@ -1556,7 +1613,7 @@
 }
 
 grpc::string GetSourcePrologue(grpc_generator::File* file,
-                               const Parameters& /*params*/) {
+                               const Parameters& params) {
   grpc::string output;
   {
     // Scope the output stream so it closes and finalizes output to the string.
@@ -1565,7 +1622,9 @@
 
     vars["filename"] = file->filename();
     vars["filename_base"] = file->filename_without_ext();
-    vars["message_header_ext"] = kCppGeneratorMessageHeaderExt;
+    vars["message_header_ext"] = params.message_header_extension.empty()
+                                     ? kCppGeneratorMessageHeaderExt
+                                     : params.message_header_extension;
     vars["service_header_ext"] = kCppGeneratorServiceHeaderExt;
 
     printer->Print(vars, "// Generated by the gRPC C++ plugin.\n");
@@ -1587,7 +1646,6 @@
     // Scope the output stream so it closes and finalizes output to the string.
     auto printer = file->CreatePrinter(&output);
     std::map<grpc::string, grpc::string> vars;
-
     static const char* headers_strs[] = {
         "functional",
         "grpcpp/impl/codegen/async_stream.h",
@@ -1647,7 +1705,7 @@
                    "const $Request$* request, $Response$* response, "
                    "std::function<void(::grpc::Status)> f) {\n");
     printer->Print(*vars,
-                   "  return ::grpc::internal::CallbackUnaryCall"
+                   "  ::grpc::internal::CallbackUnaryCall"
                    "(stub_->channel_.get(), stub_->rpcmethod_$Method$_, "
                    "context, request, response, std::move(f));\n}\n\n");
 
@@ -1657,10 +1715,30 @@
                    "const ::grpc::ByteBuffer* request, $Response$* response, "
                    "std::function<void(::grpc::Status)> f) {\n");
     printer->Print(*vars,
-                   "  return ::grpc::internal::CallbackUnaryCall"
+                   "  ::grpc::internal::CallbackUnaryCall"
                    "(stub_->channel_.get(), stub_->rpcmethod_$Method$_, "
                    "context, request, response, std::move(f));\n}\n\n");
 
+    printer->Print(*vars,
+                   "void $ns$$Service$::Stub::experimental_async::$Method$("
+                   "::grpc::ClientContext* context, "
+                   "const $Request$* request, $Response$* response, "
+                   "::grpc::experimental::ClientUnaryReactor* reactor) {\n");
+    printer->Print(*vars,
+                   "  ::grpc::internal::ClientCallbackUnaryFactory::Create"
+                   "(stub_->channel_.get(), stub_->rpcmethod_$Method$_, "
+                   "context, request, response, reactor);\n}\n\n");
+
+    printer->Print(*vars,
+                   "void $ns$$Service$::Stub::experimental_async::$Method$("
+                   "::grpc::ClientContext* context, "
+                   "const ::grpc::ByteBuffer* request, $Response$* response, "
+                   "::grpc::experimental::ClientUnaryReactor* reactor) {\n");
+    printer->Print(*vars,
+                   "  ::grpc::internal::ClientCallbackUnaryFactory::Create"
+                   "(stub_->channel_.get(), stub_->rpcmethod_$Method$_, "
+                   "context, request, response, reactor);\n}\n\n");
+
     for (auto async_prefix : async_prefixes) {
       (*vars)["AsyncPrefix"] = async_prefix.prefix;
       (*vars)["AsyncStart"] = async_prefix.start;
@@ -2046,7 +2124,7 @@
 
 // TODO(mmukhi): Make sure we need parameters or not.
 grpc::string GetMockPrologue(grpc_generator::File* file,
-                             const Parameters& /*params*/) {
+                             const Parameters& params) {
   grpc::string output;
   {
     // Scope the output stream so it closes and finalizes output to the string.
@@ -2055,7 +2133,9 @@
 
     vars["filename"] = file->filename();
     vars["filename_base"] = file->filename_without_ext();
-    vars["message_header_ext"] = kCppGeneratorMessageHeaderExt;
+    vars["message_header_ext"] = params.message_header_extension.empty()
+                                     ? kCppGeneratorMessageHeaderExt
+                                     : params.message_header_extension;
     vars["service_header_ext"] = kCppGeneratorServiceHeaderExt;
 
     printer->Print(vars, "// Generated by the gRPC C++ plugin.\n");
@@ -2065,6 +2145,15 @@
 
     printer->Print(vars, "#include \"$filename_base$$message_header_ext$\"\n");
     printer->Print(vars, "#include \"$filename_base$$service_header_ext$\"\n");
+    if (params.include_import_headers) {
+      const std::vector<grpc::string> import_names = file->GetImportNames();
+      for (const auto& import_name : import_names) {
+        const grpc::string include_name =
+            ImportInludeFromProtoName(import_name);
+        printer->Print(vars, include_name.c_str());
+      }
+      printer->PrintRaw("\n");
+    }
     printer->Print(vars, file->additional_headers().c_str());
     printer->Print(vars, "\n");
   }
diff --git a/src/compiler/cpp_generator.h b/src/compiler/cpp_generator.h
index d88ef75..c1d64e6 100644
--- a/src/compiler/cpp_generator.h
+++ b/src/compiler/cpp_generator.h
@@ -56,6 +56,10 @@
   grpc::string gmock_search_path;
   // *EXPERIMENTAL* Additional include files in grpc.pb.h
   std::vector<grpc::string> additional_header_includes;
+  // By default, use "pb.h"
+  grpc::string message_header_extension;
+  // Whether to include headers corresponding to imports in source file.
+  bool include_import_headers;
 };
 
 // Return the prologue of the generated header file.
diff --git a/src/compiler/cpp_plugin.cc b/src/compiler/cpp_plugin.cc
index c8ab788..2de2745 100644
--- a/src/compiler/cpp_plugin.cc
+++ b/src/compiler/cpp_plugin.cc
@@ -18,127 +18,7 @@
 
 // Generates cpp gRPC service interface out of Protobuf IDL.
 //
-
-#include <memory>
-#include <sstream>
-
-#include "src/compiler/config.h"
-
-#include "src/compiler/cpp_generator.h"
-#include "src/compiler/generator_helpers.h"
-#include "src/compiler/protobuf_plugin.h"
-
-class CppGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
- public:
-  CppGrpcGenerator() {}
-  virtual ~CppGrpcGenerator() {}
-
-  virtual bool Generate(const grpc::protobuf::FileDescriptor* file,
-                        const grpc::string& parameter,
-                        grpc::protobuf::compiler::GeneratorContext* context,
-                        grpc::string* error) const {
-    if (file->options().cc_generic_services()) {
-      *error =
-          "cpp grpc proto compiler plugin does not work with generic "
-          "services. To generate cpp grpc APIs, please set \""
-          "cc_generic_service = false\".";
-      return false;
-    }
-
-    grpc_cpp_generator::Parameters generator_parameters;
-    generator_parameters.use_system_headers = true;
-    generator_parameters.generate_mock_code = false;
-
-    ProtoBufFile pbfile(file);
-
-    if (!parameter.empty()) {
-      std::vector<grpc::string> parameters_list =
-          grpc_generator::tokenize(parameter, ",");
-      for (auto parameter_string = parameters_list.begin();
-           parameter_string != parameters_list.end(); parameter_string++) {
-        std::vector<grpc::string> param =
-            grpc_generator::tokenize(*parameter_string, "=");
-        if (param[0] == "services_namespace") {
-          generator_parameters.services_namespace = param[1];
-        } else if (param[0] == "use_system_headers") {
-          if (param[1] == "true") {
-            generator_parameters.use_system_headers = true;
-          } else if (param[1] == "false") {
-            generator_parameters.use_system_headers = false;
-          } else {
-            *error = grpc::string("Invalid parameter: ") + *parameter_string;
-            return false;
-          }
-        } else if (param[0] == "grpc_search_path") {
-          generator_parameters.grpc_search_path = param[1];
-        } else if (param[0] == "generate_mock_code") {
-          if (param[1] == "true") {
-            generator_parameters.generate_mock_code = true;
-          } else if (param[1] != "false") {
-            *error = grpc::string("Invalid parameter: ") + *parameter_string;
-            return false;
-          }
-        } else if (param[0] == "gmock_search_path") {
-          generator_parameters.gmock_search_path = param[1];
-        } else if (param[0] == "additional_header_includes") {
-          generator_parameters.additional_header_includes =
-              grpc_generator::tokenize(param[1], ":");
-        } else {
-          *error = grpc::string("Unknown parameter: ") + *parameter_string;
-          return false;
-        }
-      }
-    }
-
-    grpc::string file_name = grpc_generator::StripProto(file->name());
-
-    grpc::string header_code =
-        grpc_cpp_generator::GetHeaderPrologue(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetHeaderIncludes(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetHeaderServices(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetHeaderEpilogue(&pbfile, generator_parameters);
-    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> header_output(
-        context->Open(file_name + ".grpc.pb.h"));
-    grpc::protobuf::io::CodedOutputStream header_coded_out(header_output.get());
-    header_coded_out.WriteRaw(header_code.data(), header_code.size());
-
-    grpc::string source_code =
-        grpc_cpp_generator::GetSourcePrologue(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetSourceIncludes(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetSourceServices(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetSourceEpilogue(&pbfile, generator_parameters);
-    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> source_output(
-        context->Open(file_name + ".grpc.pb.cc"));
-    grpc::protobuf::io::CodedOutputStream source_coded_out(source_output.get());
-    source_coded_out.WriteRaw(source_code.data(), source_code.size());
-
-    if (!generator_parameters.generate_mock_code) {
-      return true;
-    }
-    grpc::string mock_code =
-        grpc_cpp_generator::GetMockPrologue(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetMockIncludes(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetMockServices(&pbfile, generator_parameters) +
-        grpc_cpp_generator::GetMockEpilogue(&pbfile, generator_parameters);
-    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> mock_output(
-        context->Open(file_name + "_mock.grpc.pb.h"));
-    grpc::protobuf::io::CodedOutputStream mock_coded_out(mock_output.get());
-    mock_coded_out.WriteRaw(mock_code.data(), mock_code.size());
-
-    return true;
-  }
-
- private:
-  // Insert the given code into the given file at the given insertion point.
-  void Insert(grpc::protobuf::compiler::GeneratorContext* context,
-              const grpc::string& filename, const grpc::string& insertion_point,
-              const grpc::string& code) const {
-    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> output(
-        context->OpenForInsert(filename, insertion_point));
-    grpc::protobuf::io::CodedOutputStream coded_out(output.get());
-    coded_out.WriteRaw(code.data(), code.size());
-  }
-};
+#include "src/compiler/cpp_plugin.h"
 
 int main(int argc, char* argv[]) {
   CppGrpcGenerator generator;
diff --git a/src/compiler/cpp_plugin.h b/src/compiler/cpp_plugin.h
new file mode 100644
index 0000000..1cdf0b3
--- /dev/null
+++ b/src/compiler/cpp_plugin.h
@@ -0,0 +1,154 @@
+/*
+ *
+ * Copyright 2019 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_INTERNAL_COMPILER_CPP_PLUGIN_H
+#define GRPC_INTERNAL_COMPILER_CPP_PLUGIN_H
+
+#include <memory>
+#include <sstream>
+
+#include "src/compiler/config.h"
+
+#include "src/compiler/cpp_generator.h"
+#include "src/compiler/generator_helpers.h"
+#include "src/compiler/protobuf_plugin.h"
+
+// Cpp Generator for Protobug IDL
+class CppGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
+ public:
+  CppGrpcGenerator() {}
+  virtual ~CppGrpcGenerator() {}
+
+  virtual bool Generate(const grpc::protobuf::FileDescriptor* file,
+                        const grpc::string& parameter,
+                        grpc::protobuf::compiler::GeneratorContext* context,
+                        grpc::string* error) const {
+    if (file->options().cc_generic_services()) {
+      *error =
+          "cpp grpc proto compiler plugin does not work with generic "
+          "services. To generate cpp grpc APIs, please set \""
+          "cc_generic_service = false\".";
+      return false;
+    }
+
+    grpc_cpp_generator::Parameters generator_parameters;
+    generator_parameters.use_system_headers = true;
+    generator_parameters.generate_mock_code = false;
+    generator_parameters.include_import_headers = false;
+
+    ProtoBufFile pbfile(file);
+
+    if (!parameter.empty()) {
+      std::vector<grpc::string> parameters_list =
+          grpc_generator::tokenize(parameter, ",");
+      for (auto parameter_string = parameters_list.begin();
+           parameter_string != parameters_list.end(); parameter_string++) {
+        std::vector<grpc::string> param =
+            grpc_generator::tokenize(*parameter_string, "=");
+        if (param[0] == "services_namespace") {
+          generator_parameters.services_namespace = param[1];
+        } else if (param[0] == "use_system_headers") {
+          if (param[1] == "true") {
+            generator_parameters.use_system_headers = true;
+          } else if (param[1] == "false") {
+            generator_parameters.use_system_headers = false;
+          } else {
+            *error = grpc::string("Invalid parameter: ") + *parameter_string;
+            return false;
+          }
+        } else if (param[0] == "grpc_search_path") {
+          generator_parameters.grpc_search_path = param[1];
+        } else if (param[0] == "generate_mock_code") {
+          if (param[1] == "true") {
+            generator_parameters.generate_mock_code = true;
+          } else if (param[1] != "false") {
+            *error = grpc::string("Invalid parameter: ") + *parameter_string;
+            return false;
+          }
+        } else if (param[0] == "gmock_search_path") {
+          generator_parameters.gmock_search_path = param[1];
+        } else if (param[0] == "additional_header_includes") {
+          generator_parameters.additional_header_includes =
+              grpc_generator::tokenize(param[1], ":");
+        } else if (param[0] == "message_header_extension") {
+          generator_parameters.message_header_extension = param[1];
+        } else if (param[0] == "include_import_headers") {
+          if (param[1] == "true") {
+            generator_parameters.include_import_headers = true;
+          } else if (param[1] != "false") {
+            *error = grpc::string("Invalid parameter: ") + *parameter_string;
+            return false;
+          }
+        } else {
+          *error = grpc::string("Unknown parameter: ") + *parameter_string;
+          return false;
+        }
+      }
+    }
+
+    grpc::string file_name = grpc_generator::StripProto(file->name());
+
+    grpc::string header_code =
+        grpc_cpp_generator::GetHeaderPrologue(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetHeaderIncludes(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetHeaderServices(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetHeaderEpilogue(&pbfile, generator_parameters);
+    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> header_output(
+        context->Open(file_name + ".grpc.pb.h"));
+    grpc::protobuf::io::CodedOutputStream header_coded_out(header_output.get());
+    header_coded_out.WriteRaw(header_code.data(), header_code.size());
+
+    grpc::string source_code =
+        grpc_cpp_generator::GetSourcePrologue(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetSourceIncludes(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetSourceServices(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetSourceEpilogue(&pbfile, generator_parameters);
+    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> source_output(
+        context->Open(file_name + ".grpc.pb.cc"));
+    grpc::protobuf::io::CodedOutputStream source_coded_out(source_output.get());
+    source_coded_out.WriteRaw(source_code.data(), source_code.size());
+
+    if (!generator_parameters.generate_mock_code) {
+      return true;
+    }
+    grpc::string mock_code =
+        grpc_cpp_generator::GetMockPrologue(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetMockIncludes(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetMockServices(&pbfile, generator_parameters) +
+        grpc_cpp_generator::GetMockEpilogue(&pbfile, generator_parameters);
+    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> mock_output(
+        context->Open(file_name + "_mock.grpc.pb.h"));
+    grpc::protobuf::io::CodedOutputStream mock_coded_out(mock_output.get());
+    mock_coded_out.WriteRaw(mock_code.data(), mock_code.size());
+
+    return true;
+  }
+
+ private:
+  // Insert the given code into the given file at the given insertion point.
+  void Insert(grpc::protobuf::compiler::GeneratorContext* context,
+              const grpc::string& filename, const grpc::string& insertion_point,
+              const grpc::string& code) const {
+    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> output(
+        context->OpenForInsert(filename, insertion_point));
+    grpc::protobuf::io::CodedOutputStream coded_out(output.get());
+    coded_out.WriteRaw(code.data(), code.size());
+  }
+};
+
+#endif  // GRPC_INTERNAL_COMPILER_CPP_PLUGIN_H
diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc
index ac0af33..778e5c3 100644
--- a/src/compiler/csharp_generator.cc
+++ b/src/compiler/csharp_generator.cc
@@ -382,6 +382,10 @@
       "/// <summary>Base class for server-side implementations of "
       "$servicename$</summary>\n",
       "servicename", GetServiceClassName(service));
+  out->Print(
+      "[grpc::BindServiceMethod(typeof($classname$), "
+      "\"BindService\")]\n",
+      "classname", GetServiceClassName(service));
   out->Print("public abstract partial class $name$\n", "name",
              GetServerClassName(service));
   out->Print("{\n");
diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc
index e806f46..24845ec 100644
--- a/src/compiler/objective_c_generator.cc
+++ b/src/compiler/objective_c_generator.cc
@@ -50,7 +50,8 @@
 }
 
 template <typename DescriptorType>
-static void PrintAllComments(const DescriptorType* desc, Printer* printer) {
+static void PrintAllComments(const DescriptorType* desc, Printer* printer,
+                             bool deprecated = false) {
   std::vector<grpc::string> comments;
   grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_LEADING_DETACHED,
                              &comments);
@@ -70,13 +71,20 @@
     }
     printer->Print("\n");
   }
+  if (deprecated) {
+    printer->Print(" *\n");
+    printer->Print(
+        " * This method belongs to a set of APIs that have been deprecated. "
+        "Using"
+        " the v2 API is recommended.\n");
+  }
   printer->Print(" */\n");
 }
 
 void PrintMethodSignature(Printer* printer, const MethodDescriptor* method,
                           const map< ::grpc::string, ::grpc::string>& vars) {
   // Print comment
-  PrintAllComments(method, printer);
+  PrintAllComments(method, printer, true);
 
   printer->Print(vars, "- ($return_type$)$method_name$With");
   if (method->client_streaming()) {
@@ -278,6 +286,13 @@
   map< ::grpc::string, ::grpc::string> vars = {
       {"service_class", ServiceClassName(service)}};
 
+  printer.Print(vars,
+                "/**\n"
+                " * The methods in this protocol belong to a set of old APIs "
+                "that have been deprecated. They do not\n"
+                " * recognize call options provided in the initializer. Using "
+                "the v2 protocol is recommended.\n"
+                " */\n");
   printer.Print(vars, "@protocol $service_class$ <NSObject>\n\n");
   for (int i = 0; i < service->method_count(); i++) {
     PrintMethodDeclarations(&printer, service->method(i));
@@ -329,10 +344,13 @@
       "callOptions:(GRPCCallOptions "
       "*_Nullable)callOptions"
       " NS_DESIGNATED_INITIALIZER;\n");
-  printer.Print("- (instancetype)initWithHost:(NSString *)host;\n");
   printer.Print(
       "+ (instancetype)serviceWithHost:(NSString *)host "
       "callOptions:(GRPCCallOptions *_Nullable)callOptions;\n");
+  printer.Print(
+      "// The following methods belong to a set of old APIs that have been "
+      "deprecated.\n");
+  printer.Print("- (instancetype)initWithHost:(NSString *)host;\n");
   printer.Print("+ (instancetype)serviceWithHost:(NSString *)host;\n");
   printer.Print("@end\n");
 
diff --git a/src/compiler/protobuf_plugin.h b/src/compiler/protobuf_plugin.h
index a3e448a..06cda5f 100644
--- a/src/compiler/protobuf_plugin.h
+++ b/src/compiler/protobuf_plugin.h
@@ -55,22 +55,23 @@
     return method_->output_type()->file()->name();
   }
 
-  bool get_module_and_message_path_input(grpc::string* str,
-                                         grpc::string generator_file_name,
-                                         bool generate_in_pb2_grpc,
-                                         grpc::string import_prefix) const {
+  // TODO(https://github.com/grpc/grpc/issues/18800): Clean this up.
+  bool get_module_and_message_path_input(
+      grpc::string* str, grpc::string generator_file_name,
+      bool generate_in_pb2_grpc, grpc::string import_prefix,
+      const std::vector<grpc::string>& prefixes_to_filter) const final {
     return grpc_python_generator::GetModuleAndMessagePath(
         method_->input_type(), str, generator_file_name, generate_in_pb2_grpc,
-        import_prefix);
+        import_prefix, prefixes_to_filter);
   }
 
-  bool get_module_and_message_path_output(grpc::string* str,
-                                          grpc::string generator_file_name,
-                                          bool generate_in_pb2_grpc,
-                                          grpc::string import_prefix) const {
+  bool get_module_and_message_path_output(
+      grpc::string* str, grpc::string generator_file_name,
+      bool generate_in_pb2_grpc, grpc::string import_prefix,
+      const std::vector<grpc::string>& prefixes_to_filter) const final {
     return grpc_python_generator::GetModuleAndMessagePath(
         method_->output_type(), str, generator_file_name, generate_in_pb2_grpc,
-        import_prefix);
+        import_prefix, prefixes_to_filter);
   }
 
   bool NoStreaming() const {
@@ -189,6 +190,15 @@
     return grpc_python_generator::get_all_comments(file_);
   }
 
+  vector<grpc::string> GetImportNames() const {
+    vector<grpc::string> proto_names;
+    for (int i = 0; i < file_->dependency_count(); ++i) {
+      const auto& dep = *file_->dependency(i);
+      proto_names.push_back(dep.name());
+    }
+    return proto_names;
+  }
+
  private:
   const grpc::protobuf::FileDescriptor* file_;
 };
diff --git a/src/compiler/python_generator.cc b/src/compiler/python_generator.cc
index 8a0b889..705aef1 100644
--- a/src/compiler/python_generator.cc
+++ b/src/compiler/python_generator.cc
@@ -214,13 +214,15 @@
       grpc::string input_message_module_and_class;
       if (!method->get_module_and_message_path_input(
               &input_message_module_and_class, generator_file_name,
-              generate_in_pb2_grpc, config.import_prefix)) {
+              generate_in_pb2_grpc, config.import_prefix,
+              config.prefixes_to_filter)) {
         return false;
       }
       grpc::string output_message_module_and_class;
       if (!method->get_module_and_message_path_output(
               &output_message_module_and_class, generator_file_name,
-              generate_in_pb2_grpc, config.import_prefix)) {
+              generate_in_pb2_grpc, config.import_prefix,
+              config.prefixes_to_filter)) {
         return false;
       }
       method_implementation_constructors.insert(
@@ -320,13 +322,15 @@
       grpc::string input_message_module_and_class;
       if (!method->get_module_and_message_path_input(
               &input_message_module_and_class, generator_file_name,
-              generate_in_pb2_grpc, config.import_prefix)) {
+              generate_in_pb2_grpc, config.import_prefix,
+              config.prefixes_to_filter)) {
         return false;
       }
       grpc::string output_message_module_and_class;
       if (!method->get_module_and_message_path_output(
               &output_message_module_and_class, generator_file_name,
-              generate_in_pb2_grpc, config.import_prefix)) {
+              generate_in_pb2_grpc, config.import_prefix,
+              config.prefixes_to_filter)) {
         return false;
       }
       method_cardinalities.insert(
@@ -425,13 +429,15 @@
         grpc::string request_module_and_class;
         if (!method->get_module_and_message_path_input(
                 &request_module_and_class, generator_file_name,
-                generate_in_pb2_grpc, config.import_prefix)) {
+                generate_in_pb2_grpc, config.import_prefix,
+                config.prefixes_to_filter)) {
           return false;
         }
         grpc::string response_module_and_class;
         if (!method->get_module_and_message_path_output(
                 &response_module_and_class, generator_file_name,
-                generate_in_pb2_grpc, config.import_prefix)) {
+                generate_in_pb2_grpc, config.import_prefix,
+                config.prefixes_to_filter)) {
           return false;
         }
         StringMap method_dict;
@@ -516,13 +522,15 @@
         grpc::string request_module_and_class;
         if (!method->get_module_and_message_path_input(
                 &request_module_and_class, generator_file_name,
-                generate_in_pb2_grpc, config.import_prefix)) {
+                generate_in_pb2_grpc, config.import_prefix,
+                config.prefixes_to_filter)) {
           return false;
         }
         grpc::string response_module_and_class;
         if (!method->get_module_and_message_path_output(
                 &response_module_and_class, generator_file_name,
-                generate_in_pb2_grpc, config.import_prefix)) {
+                generate_in_pb2_grpc, config.import_prefix,
+                config.prefixes_to_filter)) {
           return false;
         }
         StringMap method_dict;
@@ -589,17 +597,21 @@
 
         grpc::string input_type_file_name = method->get_input_type_name();
         grpc::string input_module_name =
-            ModuleName(input_type_file_name, config.import_prefix);
+            ModuleName(input_type_file_name, config.import_prefix,
+                       config.prefixes_to_filter);
         grpc::string input_module_alias =
-            ModuleAlias(input_type_file_name, config.import_prefix);
+            ModuleAlias(input_type_file_name, config.import_prefix,
+                        config.prefixes_to_filter);
         imports_set.insert(
             std::make_tuple(input_module_name, input_module_alias));
 
         grpc::string output_type_file_name = method->get_output_type_name();
         grpc::string output_module_name =
-            ModuleName(output_type_file_name, config.import_prefix);
+            ModuleName(output_type_file_name, config.import_prefix,
+                       config.prefixes_to_filter);
         grpc::string output_module_alias =
-            ModuleAlias(output_type_file_name, config.import_prefix);
+            ModuleAlias(output_type_file_name, config.import_prefix,
+                        config.prefixes_to_filter);
         imports_set.insert(
             std::make_tuple(output_module_name, output_module_alias));
       }
diff --git a/src/compiler/python_generator.h b/src/compiler/python_generator.h
index 9139307..6077ac4 100644
--- a/src/compiler/python_generator.h
+++ b/src/compiler/python_generator.h
@@ -20,6 +20,7 @@
 #define GRPC_INTERNAL_COMPILER_PYTHON_GENERATOR_H
 
 #include <utility>
+#include <vector>
 
 #include "src/compiler/config.h"
 #include "src/compiler/schema_interface.h"
@@ -35,6 +36,7 @@
   grpc::string beta_package_root;
   // TODO(https://github.com/google/protobuf/issues/888): Drop this.
   grpc::string import_prefix;
+  std::vector<grpc::string> prefixes_to_filter;
 };
 
 class PythonGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
diff --git a/src/compiler/python_generator_helpers.h b/src/compiler/python_generator_helpers.h
index b1b58be..171dd73 100644
--- a/src/compiler/python_generator_helpers.h
+++ b/src/compiler/python_generator_helpers.h
@@ -49,23 +49,39 @@
 typedef vector<const Descriptor*> DescriptorVector;
 typedef vector<grpc::string> StringVector;
 
+static grpc::string StripModulePrefixes(
+    const grpc::string& raw_module_name,
+    const std::vector<grpc::string>& prefixes_to_filter) {
+  for (const auto& prefix : prefixes_to_filter) {
+    if (raw_module_name.rfind(prefix, 0) == 0) {
+      return raw_module_name.substr(prefix.size(),
+                                    raw_module_name.size() - prefix.size());
+    }
+  }
+  return raw_module_name;
+}
+
 // TODO(https://github.com/google/protobuf/issues/888):
 // Export `ModuleName` from protobuf's
 // `src/google/protobuf/compiler/python/python_generator.cc` file.
 grpc::string ModuleName(const grpc::string& filename,
-                        const grpc::string& import_prefix) {
+                        const grpc::string& import_prefix,
+                        const std::vector<grpc::string>& prefixes_to_filter) {
   grpc::string basename = StripProto(filename);
   basename = StringReplace(basename, "-", "_");
   basename = StringReplace(basename, "/", ".");
-  return import_prefix + basename + "_pb2";
+  return StripModulePrefixes(import_prefix + basename + "_pb2",
+                             prefixes_to_filter);
 }
 
 // TODO(https://github.com/google/protobuf/issues/888):
 // Export `ModuleAlias` from protobuf's
 // `src/google/protobuf/compiler/python/python_generator.cc` file.
 grpc::string ModuleAlias(const grpc::string& filename,
-                         const grpc::string& import_prefix) {
-  grpc::string module_name = ModuleName(filename, import_prefix);
+                         const grpc::string& import_prefix,
+                         const std::vector<grpc::string>& prefixes_to_filter) {
+  grpc::string module_name =
+      ModuleName(filename, import_prefix, prefixes_to_filter);
   // We can't have dots in the module name, so we replace each with _dot_.
   // But that could lead to a collision between a.b and a_dot_b, so we also
   // duplicate each underscore.
@@ -74,10 +90,10 @@
   return module_name;
 }
 
-bool GetModuleAndMessagePath(const Descriptor* type, grpc::string* out,
-                             grpc::string generator_file_name,
-                             bool generate_in_pb2_grpc,
-                             grpc::string& import_prefix) {
+bool GetModuleAndMessagePath(
+    const Descriptor* type, grpc::string* out, grpc::string generator_file_name,
+    bool generate_in_pb2_grpc, grpc::string& import_prefix,
+    const std::vector<grpc::string>& prefixes_to_filter) {
   const Descriptor* path_elem_type = type;
   DescriptorVector message_path;
   do {
@@ -93,7 +109,7 @@
 
   grpc::string module;
   if (generator_file_name != file_name || generate_in_pb2_grpc) {
-    module = ModuleAlias(file_name, import_prefix) + ".";
+    module = ModuleAlias(file_name, import_prefix, prefixes_to_filter) + ".";
   } else {
     module = "";
   }
diff --git a/src/compiler/schema_interface.h b/src/compiler/schema_interface.h
index c000478..e7b427d 100644
--- a/src/compiler/schema_interface.h
+++ b/src/compiler/schema_interface.h
@@ -57,10 +57,12 @@
 
   virtual bool get_module_and_message_path_input(
       grpc::string* str, grpc::string generator_file_name,
-      bool generate_in_pb2_grpc, grpc::string import_prefix) const = 0;
+      bool generate_in_pb2_grpc, grpc::string import_prefix,
+      const std::vector<grpc::string>& prefixes_to_filter) const = 0;
   virtual bool get_module_and_message_path_output(
       grpc::string* str, grpc::string generator_file_name,
-      bool generate_in_pb2_grpc, grpc::string import_prefix) const = 0;
+      bool generate_in_pb2_grpc, grpc::string import_prefix,
+      const std::vector<grpc::string>& prefixes_to_filter) const = 0;
 
   virtual grpc::string get_input_type_name() const = 0;
   virtual grpc::string get_output_type_name() const = 0;
@@ -101,6 +103,7 @@
   virtual grpc::string package() const = 0;
   virtual std::vector<grpc::string> package_parts() const = 0;
   virtual grpc::string additional_headers() const = 0;
+  virtual std::vector<grpc::string> GetImportNames() const { return {}; }
 
   virtual int service_count() const = 0;
   virtual std::unique_ptr<const Service> service(int i) const = 0;
diff --git a/src/core/ext/filters/client_channel/backup_poller.cc b/src/core/ext/filters/client_channel/backup_poller.cc
index 3e2faa5..dd76169 100644
--- a/src/core/ext/filters/client_channel/backup_poller.cc
+++ b/src/core/ext/filters/client_channel/backup_poller.cc
@@ -25,8 +25,8 @@
 #include <grpc/support/log.h>
 #include <grpc/support/sync.h>
 #include "src/core/ext/filters/client_channel/client_channel.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/iomgr/error.h"
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/timer.h"
@@ -56,21 +56,27 @@
 // treated as const.
 static int g_poll_interval_ms = DEFAULT_POLL_INTERVAL_MS;
 
+GPR_GLOBAL_CONFIG_DEFINE_INT32(
+    grpc_client_channel_backup_poll_interval_ms, DEFAULT_POLL_INTERVAL_MS,
+    "Declares the interval in ms between two backup polls on client channels. "
+    "These polls are run in the timer thread so that gRPC can process "
+    "connection failures while there is no active polling thread. "
+    "They help reconnect disconnected client channels (mostly due to "
+    "idleness), so that the next RPC on this channel won't fail. Set to 0 to "
+    "turn off the backup polls.");
+
 static void init_globals() {
   gpr_mu_init(&g_poller_mu);
-  char* env = gpr_getenv("GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS");
-  if (env != nullptr) {
-    int poll_interval_ms = gpr_parse_nonnegative_int(env);
-    if (poll_interval_ms == -1) {
-      gpr_log(GPR_ERROR,
-              "Invalid GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS: %s, "
-              "default value %d will be used.",
-              env, g_poll_interval_ms);
-    } else {
-      g_poll_interval_ms = poll_interval_ms;
-    }
+  int32_t poll_interval_ms =
+      GPR_GLOBAL_CONFIG_GET(grpc_client_channel_backup_poll_interval_ms);
+  if (poll_interval_ms < 0) {
+    gpr_log(GPR_ERROR,
+            "Invalid GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS: %d, "
+            "default value %d will be used.",
+            poll_interval_ms, g_poll_interval_ms);
+  } else {
+    g_poll_interval_ms = poll_interval_ms;
   }
-  gpr_free(env);
 }
 
 static void backup_poller_shutdown_unref(backup_poller* p) {
diff --git a/src/core/ext/filters/client_channel/backup_poller.h b/src/core/ext/filters/client_channel/backup_poller.h
index 8f132f9..e1bf4f8 100644
--- a/src/core/ext/filters/client_channel/backup_poller.h
+++ b/src/core/ext/filters/client_channel/backup_poller.h
@@ -23,6 +23,9 @@
 
 #include <grpc/grpc.h>
 #include "src/core/lib/channel/channel_stack.h"
+#include "src/core/lib/gprpp/global_config.h"
+
+GPR_GLOBAL_CONFIG_DECLARE_INT32(grpc_client_channel_backup_poll_interval_ms);
 
 /* Start polling \a interested_parties periodically in the timer thread  */
 void grpc_client_channel_start_backup_polling(
diff --git a/src/core/ext/filters/client_channel/channel_connectivity.cc b/src/core/ext/filters/client_channel/channel_connectivity.cc
index 9f970f6..232183d 100644
--- a/src/core/ext/filters/client_channel/channel_connectivity.cc
+++ b/src/core/ext/filters/client_channel/channel_connectivity.cc
@@ -125,7 +125,7 @@
   gpr_mu_lock(&w->mu);
 
   if (due_to_completion) {
-    if (grpc_trace_operation_failures.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures)) {
       GRPC_LOG_IF_ERROR("watch_completion_error", GRPC_ERROR_REF(error));
     }
     GRPC_ERROR_UNREF(error);
diff --git a/src/core/ext/filters/client_channel/client_channel.cc b/src/core/ext/filters/client_channel/client_channel.cc
index 82ce253..f774c98 100644
--- a/src/core/ext/filters/client_channel/client_channel.cc
+++ b/src/core/ext/filters/client_channel/client_channel.cc
@@ -51,6 +51,7 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/iomgr.h"
 #include "src/core/lib/iomgr/polling_entity.h"
@@ -65,14 +66,12 @@
 #include "src/core/lib/transport/static_metadata.h"
 #include "src/core/lib/transport/status_metadata.h"
 
-using grpc_core::internal::ClientChannelMethodParams;
-using grpc_core::internal::ClientChannelMethodParamsTable;
-using grpc_core::internal::ProcessedResolverResult;
+using grpc_core::internal::ClientChannelMethodParsedObject;
 using grpc_core::internal::ServerRetryThrottleData;
 
-using grpc_core::LoadBalancingPolicy;
-
-/* Client channel implementation */
+//
+// Client channel filter
+//
 
 // By default, we buffer 256 KiB per RPC for retries.
 // TODO(roth): Do we have any data to suggest a better value?
@@ -82,160 +81,916 @@
 // any even moderately compelling reason to do so.
 #define RETRY_BACKOFF_JITTER 0.2
 
-grpc_core::TraceFlag grpc_client_channel_call_trace(false,
-                                                    "client_channel_call");
-grpc_core::TraceFlag grpc_client_channel_routing_trace(
-    false, "client_channel_routing");
-
-/*************************************************************************
- * CHANNEL-WIDE FUNCTIONS
- */
-
-struct external_connectivity_watcher;
-
-struct QueuedPick {
-  LoadBalancingPolicy::PickArgs pick;
-  grpc_call_element* elem;
-  QueuedPick* next = nullptr;
-};
-
-typedef struct client_channel_channel_data {
-  bool deadline_checking_enabled;
-  bool enable_retries;
-  size_t per_rpc_retry_buffer_size;
-
-  /** combiner protecting all variables below in this data structure */
-  grpc_combiner* combiner;
-  /** owning stack */
-  grpc_channel_stack* owning_stack;
-  /** interested parties (owned) */
-  grpc_pollset_set* interested_parties;
-  // Client channel factory.
-  grpc_core::ClientChannelFactory* client_channel_factory;
-  // Subchannel pool.
-  grpc_core::RefCountedPtr<grpc_core::SubchannelPoolInterface> subchannel_pool;
-
-  grpc_core::channelz::ClientChannelNode* channelz_node;
-
-  // Resolving LB policy.
-  grpc_core::OrphanablePtr<LoadBalancingPolicy> resolving_lb_policy;
-  // Subchannel picker from LB policy.
-  grpc_core::UniquePtr<LoadBalancingPolicy::SubchannelPicker> picker;
-  // Linked list of queued picks.
-  QueuedPick* queued_picks;
-
-  bool have_service_config;
-  /** retry throttle data from service config */
-  grpc_core::RefCountedPtr<ServerRetryThrottleData> retry_throttle_data;
-  /** per-method service config data */
-  grpc_core::RefCountedPtr<ClientChannelMethodParamsTable> method_params_table;
-
-  /* the following properties are guarded by a mutex since APIs require them
-     to be instantaneously available */
-  gpr_mu info_mu;
-  grpc_core::UniquePtr<char> info_lb_policy_name;
-  grpc_core::UniquePtr<char> info_service_config_json;
-
-  grpc_connectivity_state_tracker state_tracker;
-  grpc_error* disconnect_error;
-
-  /* external_connectivity_watcher_list head is guarded by its own mutex, since
-   * counts need to be grabbed immediately without polling on a cq */
-  gpr_mu external_connectivity_watcher_list_mu;
-  struct external_connectivity_watcher* external_connectivity_watcher_list_head;
-} channel_data;
-
-// Forward declarations.
-static void start_pick_locked(void* arg, grpc_error* ignored);
-static void maybe_apply_service_config_to_call_locked(grpc_call_element* elem);
-
-static const char* get_channel_connectivity_state_change_string(
-    grpc_connectivity_state state) {
-  switch (state) {
-    case GRPC_CHANNEL_IDLE:
-      return "Channel state change to IDLE";
-    case GRPC_CHANNEL_CONNECTING:
-      return "Channel state change to CONNECTING";
-    case GRPC_CHANNEL_READY:
-      return "Channel state change to READY";
-    case GRPC_CHANNEL_TRANSIENT_FAILURE:
-      return "Channel state change to TRANSIENT_FAILURE";
-    case GRPC_CHANNEL_SHUTDOWN:
-      return "Channel state change to SHUTDOWN";
-  }
-  GPR_UNREACHABLE_CODE(return "UNKNOWN");
-}
-
-static void set_connectivity_state_and_picker_locked(
-    channel_data* chand, grpc_connectivity_state state, grpc_error* state_error,
-    const char* reason,
-    grpc_core::UniquePtr<LoadBalancingPolicy::SubchannelPicker> picker) {
-  // Update connectivity state.
-  grpc_connectivity_state_set(&chand->state_tracker, state, state_error,
-                              reason);
-  if (chand->channelz_node != nullptr) {
-    chand->channelz_node->AddTraceEvent(
-        grpc_core::channelz::ChannelTrace::Severity::Info,
-        grpc_slice_from_static_string(
-            get_channel_connectivity_state_change_string(state)));
-  }
-  // Update picker.
-  chand->picker = std::move(picker);
-  // Re-process queued picks.
-  for (QueuedPick* pick = chand->queued_picks; pick != nullptr;
-       pick = pick->next) {
-    start_pick_locked(pick->elem, GRPC_ERROR_NONE);
-  }
-}
+// 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
+//   send_initial_metadata
+//   recv_message
+//   send_message
+//   recv_trailing_metadata
+//   send_trailing_metadata
+#define MAX_PENDING_BATCHES 6
 
 namespace grpc_core {
+
+TraceFlag grpc_client_channel_call_trace(false, "client_channel_call");
+TraceFlag grpc_client_channel_routing_trace(false, "client_channel_routing");
+
 namespace {
 
-class ClientChannelControlHelper
+//
+// ChannelData definition
+//
+
+class ChannelData {
+ public:
+  struct QueuedPick {
+    LoadBalancingPolicy::PickArgs pick;
+    grpc_call_element* elem;
+    QueuedPick* next = nullptr;
+  };
+
+  static grpc_error* Init(grpc_channel_element* elem,
+                          grpc_channel_element_args* args);
+  static void Destroy(grpc_channel_element* elem);
+  static void StartTransportOp(grpc_channel_element* elem,
+                               grpc_transport_op* op);
+  static void GetChannelInfo(grpc_channel_element* elem,
+                             const grpc_channel_info* info);
+
+  void set_channelz_node(channelz::ClientChannelNode* node) {
+    channelz_node_ = node;
+    resolving_lb_policy_->set_channelz_node(node->Ref());
+  }
+  void FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels,
+                                channelz::ChildRefsList* child_channels) {
+    if (resolving_lb_policy_ != nullptr) {
+      resolving_lb_policy_->FillChildRefsForChannelz(child_subchannels,
+                                                     child_channels);
+    }
+  }
+
+  bool deadline_checking_enabled() const { return deadline_checking_enabled_; }
+  bool enable_retries() const { return enable_retries_; }
+  size_t per_rpc_retry_buffer_size() const {
+    return per_rpc_retry_buffer_size_;
+  }
+
+  // Note: Does NOT return a new ref.
+  grpc_error* disconnect_error() const {
+    return disconnect_error_.Load(MemoryOrder::ACQUIRE);
+  }
+
+  grpc_combiner* data_plane_combiner() const { return data_plane_combiner_; }
+
+  LoadBalancingPolicy::SubchannelPicker* picker() const {
+    return picker_.get();
+  }
+  void AddQueuedPick(QueuedPick* pick, grpc_polling_entity* pollent);
+  void RemoveQueuedPick(QueuedPick* to_remove, grpc_polling_entity* pollent);
+
+  bool received_service_config_data() const {
+    return received_service_config_data_;
+  }
+  RefCountedPtr<ServerRetryThrottleData> retry_throttle_data() const {
+    return retry_throttle_data_;
+  }
+  RefCountedPtr<ServiceConfig> service_config() const {
+    return service_config_;
+  }
+
+  grpc_connectivity_state CheckConnectivityState(bool try_to_connect);
+  void AddExternalConnectivityWatcher(grpc_polling_entity pollent,
+                                      grpc_connectivity_state* state,
+                                      grpc_closure* on_complete,
+                                      grpc_closure* watcher_timer_init) {
+    // Will delete itself.
+    New<ExternalConnectivityWatcher>(this, pollent, state, on_complete,
+                                     watcher_timer_init);
+  }
+  int NumExternalConnectivityWatchers() const {
+    return external_connectivity_watcher_list_.size();
+  }
+
+ private:
+  class ConnectivityStateAndPickerSetter;
+  class ServiceConfigSetter;
+  class ClientChannelControlHelper;
+
+  class ExternalConnectivityWatcher {
+   public:
+    class WatcherList {
+     public:
+      WatcherList() { gpr_mu_init(&mu_); }
+      ~WatcherList() { gpr_mu_destroy(&mu_); }
+
+      int size() const;
+      ExternalConnectivityWatcher* Lookup(grpc_closure* on_complete) const;
+      void Add(ExternalConnectivityWatcher* watcher);
+      void Remove(const ExternalConnectivityWatcher* watcher);
+
+     private:
+      // head_ is guarded by a mutex, since the size() method needs to
+      // iterate over the list, and it's called from the C-core API
+      // function grpc_channel_num_external_connectivity_watchers(), which
+      // is synchronous and therefore cannot run in the combiner.
+      mutable gpr_mu mu_;
+      ExternalConnectivityWatcher* head_ = nullptr;
+    };
+
+    ExternalConnectivityWatcher(ChannelData* chand, grpc_polling_entity pollent,
+                                grpc_connectivity_state* state,
+                                grpc_closure* on_complete,
+                                grpc_closure* watcher_timer_init);
+
+    ~ExternalConnectivityWatcher();
+
+   private:
+    static void OnWatchCompleteLocked(void* arg, grpc_error* error);
+    static void WatchConnectivityStateLocked(void* arg, grpc_error* ignored);
+
+    ChannelData* chand_;
+    grpc_polling_entity pollent_;
+    grpc_connectivity_state* state_;
+    grpc_closure* on_complete_;
+    grpc_closure* watcher_timer_init_;
+    grpc_closure my_closure_;
+    ExternalConnectivityWatcher* next_ = nullptr;
+  };
+
+  ChannelData(grpc_channel_element_args* args, grpc_error** error);
+  ~ChannelData();
+
+  static bool ProcessResolverResultLocked(
+      void* arg, const Resolver::Result& result, const char** lb_policy_name,
+      RefCountedPtr<ParsedLoadBalancingConfig>* lb_policy_config,
+      grpc_error** service_config_error);
+
+  grpc_error* DoPingLocked(grpc_transport_op* op);
+
+  static void StartTransportOpLocked(void* arg, grpc_error* ignored);
+
+  static void TryToConnectLocked(void* arg, grpc_error* error_ignored);
+
+  void ProcessLbPolicy(
+      const Resolver::Result& resolver_result,
+      const internal::ClientChannelGlobalParsedObject* parsed_service_config,
+      UniquePtr<char>* lb_policy_name,
+      RefCountedPtr<ParsedLoadBalancingConfig>* lb_policy_config);
+
+  //
+  // Fields set at construction and never modified.
+  //
+  const bool deadline_checking_enabled_;
+  const bool enable_retries_;
+  const size_t per_rpc_retry_buffer_size_;
+  grpc_channel_stack* owning_stack_;
+  ClientChannelFactory* client_channel_factory_;
+  UniquePtr<char> server_name_;
+  RefCountedPtr<ServiceConfig> default_service_config_;
+  // Initialized shortly after construction.
+  channelz::ClientChannelNode* channelz_node_ = nullptr;
+
+  //
+  // Fields used in the data plane.  Guarded by data_plane_combiner.
+  //
+  grpc_combiner* data_plane_combiner_;
+  UniquePtr<LoadBalancingPolicy::SubchannelPicker> picker_;
+  QueuedPick* queued_picks_ = nullptr;  // Linked list of queued picks.
+  // Data from service config.
+  bool received_service_config_data_ = false;
+  RefCountedPtr<ServerRetryThrottleData> retry_throttle_data_;
+  RefCountedPtr<ServiceConfig> service_config_;
+
+  //
+  // Fields used in the control plane.  Guarded by combiner.
+  //
+  grpc_combiner* combiner_;
+  grpc_pollset_set* interested_parties_;
+  RefCountedPtr<SubchannelPoolInterface> subchannel_pool_;
+  OrphanablePtr<LoadBalancingPolicy> resolving_lb_policy_;
+  grpc_connectivity_state_tracker state_tracker_;
+  ExternalConnectivityWatcher::WatcherList external_connectivity_watcher_list_;
+  UniquePtr<char> health_check_service_name_;
+  RefCountedPtr<ServiceConfig> saved_service_config_;
+  bool received_first_resolver_result_ = false;
+
+  //
+  // Fields accessed from both data plane and control plane combiners.
+  //
+  Atomic<grpc_error*> disconnect_error_;
+
+  //
+  // Fields guarded by a mutex, since they need to be accessed
+  // synchronously via get_channel_info().
+  //
+  gpr_mu info_mu_;
+  UniquePtr<char> info_lb_policy_name_;
+  UniquePtr<char> info_service_config_json_;
+};
+
+//
+// CallData definition
+//
+
+class CallData {
+ public:
+  static grpc_error* Init(grpc_call_element* elem,
+                          const grpc_call_element_args* args);
+  static void Destroy(grpc_call_element* elem,
+                      const grpc_call_final_info* final_info,
+                      grpc_closure* then_schedule_closure);
+  static void StartTransportStreamOpBatch(
+      grpc_call_element* elem, grpc_transport_stream_op_batch* batch);
+  static void SetPollent(grpc_call_element* elem, grpc_polling_entity* pollent);
+
+  RefCountedPtr<SubchannelCall> subchannel_call() { return subchannel_call_; }
+
+  // Invoked by channel for queued picks once resolver results are available.
+  void MaybeApplyServiceConfigToCallLocked(grpc_call_element* elem);
+
+  // Invoked by channel for queued picks when the picker is updated.
+  static void StartPickLocked(void* arg, grpc_error* error);
+
+ private:
+  class QueuedPickCanceller;
+
+  // State used for starting a retryable batch on a subchannel call.
+  // This provides its own grpc_transport_stream_op_batch and other data
+  // structures needed to populate the ops in the batch.
+  // We allocate one struct on the arena for each attempt at starting a
+  // batch on a given subchannel call.
+  struct SubchannelCallBatchData {
+    // Creates a SubchannelCallBatchData object on the call's arena with the
+    // specified refcount.  If set_on_complete is true, the batch's
+    // on_complete callback will be set to point to on_complete();
+    // otherwise, the batch's on_complete callback will be null.
+    static SubchannelCallBatchData* Create(grpc_call_element* elem,
+                                           int refcount, bool set_on_complete);
+
+    void Unref() {
+      if (gpr_unref(&refs)) Destroy();
+    }
+
+    SubchannelCallBatchData(grpc_call_element* elem, CallData* calld,
+                            int refcount, bool set_on_complete);
+    // All dtor code must be added in `Destroy()`. This is because we may
+    // call closures in `SubchannelCallBatchData` after they are unrefed by
+    // `Unref()`, and msan would complain about accessing this class
+    // after calling dtor. As a result we cannot call the `dtor` in `Unref()`.
+    // TODO(soheil): We should try to call the dtor in `Unref()`.
+    ~SubchannelCallBatchData() { Destroy(); }
+    void Destroy();
+
+    gpr_refcount refs;
+    grpc_call_element* elem;
+    RefCountedPtr<SubchannelCall> subchannel_call;
+    // The batch to use in the subchannel call.
+    // Its payload field points to SubchannelCallRetryState::batch_payload.
+    grpc_transport_stream_op_batch batch;
+    // For intercepting on_complete.
+    grpc_closure on_complete;
+  };
+
+  // Retry state associated with a subchannel call.
+  // Stored in the parent_data of the subchannel call object.
+  struct SubchannelCallRetryState {
+    explicit SubchannelCallRetryState(grpc_call_context_element* context)
+        : batch_payload(context),
+          started_send_initial_metadata(false),
+          completed_send_initial_metadata(false),
+          started_send_trailing_metadata(false),
+          completed_send_trailing_metadata(false),
+          started_recv_initial_metadata(false),
+          completed_recv_initial_metadata(false),
+          started_recv_trailing_metadata(false),
+          completed_recv_trailing_metadata(false),
+          retry_dispatched(false) {}
+
+    // SubchannelCallBatchData.batch.payload points to this.
+    grpc_transport_stream_op_batch_payload batch_payload;
+    // For send_initial_metadata.
+    // Note that we need to make a copy of the initial metadata for each
+    // subchannel call instead of just referring to the copy in call_data,
+    // because filters in the subchannel stack will probably add entries,
+    // so we need to start in a pristine state for each attempt of the call.
+    grpc_linked_mdelem* send_initial_metadata_storage;
+    grpc_metadata_batch send_initial_metadata;
+    // For send_message.
+    // TODO(roth): Restructure this to eliminate use of ManualConstructor.
+    ManualConstructor<ByteStreamCache::CachingByteStream> send_message;
+    // For send_trailing_metadata.
+    grpc_linked_mdelem* send_trailing_metadata_storage;
+    grpc_metadata_batch send_trailing_metadata;
+    // For intercepting recv_initial_metadata.
+    grpc_metadata_batch recv_initial_metadata;
+    grpc_closure recv_initial_metadata_ready;
+    bool trailing_metadata_available = false;
+    // For intercepting recv_message.
+    grpc_closure recv_message_ready;
+    OrphanablePtr<ByteStream> recv_message;
+    // For intercepting recv_trailing_metadata.
+    grpc_metadata_batch recv_trailing_metadata;
+    grpc_transport_stream_stats collect_stats;
+    grpc_closure recv_trailing_metadata_ready;
+    // These fields indicate which ops have been started and completed on
+    // this subchannel call.
+    size_t started_send_message_count = 0;
+    size_t completed_send_message_count = 0;
+    size_t started_recv_message_count = 0;
+    size_t completed_recv_message_count = 0;
+    bool started_send_initial_metadata : 1;
+    bool completed_send_initial_metadata : 1;
+    bool started_send_trailing_metadata : 1;
+    bool completed_send_trailing_metadata : 1;
+    bool started_recv_initial_metadata : 1;
+    bool completed_recv_initial_metadata : 1;
+    bool started_recv_trailing_metadata : 1;
+    bool completed_recv_trailing_metadata : 1;
+    // State for callback processing.
+    SubchannelCallBatchData* recv_initial_metadata_ready_deferred_batch =
+        nullptr;
+    grpc_error* recv_initial_metadata_error = GRPC_ERROR_NONE;
+    SubchannelCallBatchData* recv_message_ready_deferred_batch = nullptr;
+    grpc_error* recv_message_error = GRPC_ERROR_NONE;
+    SubchannelCallBatchData* recv_trailing_metadata_internal_batch = nullptr;
+    // NOTE: Do not move this next to the metadata bitfields above. That would
+    //       save space but will also result in a data race because compiler
+    //       will generate a 2 byte store which overwrites the meta-data
+    //       fields upon setting this field.
+    bool retry_dispatched : 1;
+  };
+
+  // Pending batches stored in call data.
+  struct PendingBatch {
+    // The pending batch.  If nullptr, this slot is empty.
+    grpc_transport_stream_op_batch* batch;
+    // Indicates whether payload for send ops has been cached in CallData.
+    bool send_ops_cached;
+  };
+
+  CallData(grpc_call_element* elem, const ChannelData& chand,
+           const grpc_call_element_args& args);
+  ~CallData();
+
+  // Caches data for send ops so that it can be retried later, if not
+  // already cached.
+  void MaybeCacheSendOpsForBatch(PendingBatch* pending);
+  void FreeCachedSendInitialMetadata(ChannelData* chand);
+  // Frees cached send_message at index idx.
+  void FreeCachedSendMessage(ChannelData* chand, size_t idx);
+  void FreeCachedSendTrailingMetadata(ChannelData* chand);
+  // Frees cached send ops that have already been completed after
+  // committing the call.
+  void FreeCachedSendOpDataAfterCommit(grpc_call_element* elem,
+                                       SubchannelCallRetryState* retry_state);
+  // Frees cached send ops that were completed by the completed batch in
+  // batch_data.  Used when batches are completed after the call is committed.
+  void FreeCachedSendOpDataForCompletedBatch(
+      grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+      SubchannelCallRetryState* retry_state);
+
+  static void MaybeInjectRecvTrailingMetadataReadyForLoadBalancingPolicy(
+      const LoadBalancingPolicy::PickArgs& pick,
+      grpc_transport_stream_op_batch* batch);
+
+  // Returns the index into pending_batches_ to be used for batch.
+  static size_t GetBatchIndex(grpc_transport_stream_op_batch* batch);
+  void PendingBatchesAdd(grpc_call_element* elem,
+                         grpc_transport_stream_op_batch* batch);
+  void PendingBatchClear(PendingBatch* pending);
+  void MaybeClearPendingBatch(grpc_call_element* elem, PendingBatch* pending);
+  static void FailPendingBatchInCallCombiner(void* arg, grpc_error* error);
+  // A predicate type and some useful implementations for PendingBatchesFail().
+  typedef bool (*YieldCallCombinerPredicate)(
+      const CallCombinerClosureList& closures);
+  static bool YieldCallCombiner(const CallCombinerClosureList& closures) {
+    return true;
+  }
+  static bool NoYieldCallCombiner(const CallCombinerClosureList& closures) {
+    return false;
+  }
+  static bool YieldCallCombinerIfPendingBatchesFound(
+      const CallCombinerClosureList& closures) {
+    return closures.size() > 0;
+  }
+  // Fails all pending batches.
+  // If yield_call_combiner_predicate returns true, assumes responsibility for
+  // yielding the call combiner.
+  void PendingBatchesFail(
+      grpc_call_element* elem, grpc_error* error,
+      YieldCallCombinerPredicate yield_call_combiner_predicate);
+  static void ResumePendingBatchInCallCombiner(void* arg, grpc_error* ignored);
+  // Resumes all pending batches on subchannel_call_.
+  void PendingBatchesResume(grpc_call_element* elem);
+  // Returns a pointer to the first pending batch for which predicate(batch)
+  // returns true, or null if not found.
+  template <typename Predicate>
+  PendingBatch* PendingBatchFind(grpc_call_element* elem,
+                                 const char* log_message, Predicate predicate);
+
+  // Commits the call so that no further retry attempts will be performed.
+  void RetryCommit(grpc_call_element* elem,
+                   SubchannelCallRetryState* retry_state);
+  // Starts a retry after appropriate back-off.
+  void DoRetry(grpc_call_element* elem, SubchannelCallRetryState* retry_state,
+               grpc_millis server_pushback_ms);
+  // Returns true if the call is being retried.
+  bool MaybeRetry(grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+                  grpc_status_code status, grpc_mdelem* server_pushback_md);
+
+  // Invokes recv_initial_metadata_ready for a subchannel batch.
+  static void InvokeRecvInitialMetadataCallback(void* arg, grpc_error* error);
+  // Intercepts recv_initial_metadata_ready callback for retries.
+  // Commits the call and returns the initial metadata up the stack.
+  static void RecvInitialMetadataReady(void* arg, grpc_error* error);
+
+  // Invokes recv_message_ready for a subchannel batch.
+  static void InvokeRecvMessageCallback(void* arg, grpc_error* error);
+  // Intercepts recv_message_ready callback for retries.
+  // Commits the call and returns the message up the stack.
+  static void RecvMessageReady(void* arg, grpc_error* error);
+
+  // Sets *status and *server_pushback_md based on md_batch and error.
+  // Only sets *server_pushback_md if server_pushback_md != nullptr.
+  void GetCallStatus(grpc_call_element* elem, grpc_metadata_batch* md_batch,
+                     grpc_error* error, grpc_status_code* status,
+                     grpc_mdelem** server_pushback_md);
+  // Adds recv_trailing_metadata_ready closure to closures.
+  void AddClosureForRecvTrailingMetadataReady(
+      grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+      grpc_error* error, CallCombinerClosureList* closures);
+  // Adds any necessary closures for deferred recv_initial_metadata and
+  // recv_message callbacks to closures.
+  static void AddClosuresForDeferredRecvCallbacks(
+      SubchannelCallBatchData* batch_data,
+      SubchannelCallRetryState* retry_state, CallCombinerClosureList* closures);
+  // Returns true if any op in the batch was not yet started.
+  // Only looks at send ops, since recv ops are always started immediately.
+  bool PendingBatchIsUnstarted(PendingBatch* pending,
+                               SubchannelCallRetryState* retry_state);
+  // For any pending batch containing an op that has not yet been started,
+  // adds the pending batch's completion closures to closures.
+  void AddClosuresToFailUnstartedPendingBatches(
+      grpc_call_element* elem, SubchannelCallRetryState* retry_state,
+      grpc_error* error, CallCombinerClosureList* closures);
+  // Runs necessary closures upon completion of a call attempt.
+  void RunClosuresForCompletedCall(SubchannelCallBatchData* batch_data,
+                                   grpc_error* error);
+  // Intercepts recv_trailing_metadata_ready callback for retries.
+  // Commits the call and returns the trailing metadata up the stack.
+  static void RecvTrailingMetadataReady(void* arg, grpc_error* error);
+
+  // Adds the on_complete closure for the pending batch completed in
+  // batch_data to closures.
+  void AddClosuresForCompletedPendingBatch(
+      grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+      SubchannelCallRetryState* retry_state, grpc_error* error,
+      CallCombinerClosureList* closures);
+
+  // If there are any cached ops to replay or pending ops to start on the
+  // subchannel call, adds a closure to closures to invoke
+  // StartRetriableSubchannelBatches().
+  void AddClosuresForReplayOrPendingSendOps(
+      grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+      SubchannelCallRetryState* retry_state, CallCombinerClosureList* closures);
+
+  // Callback used to intercept on_complete from subchannel calls.
+  // Called only when retries are enabled.
+  static void OnComplete(void* arg, grpc_error* error);
+
+  static void StartBatchInCallCombiner(void* arg, grpc_error* ignored);
+  // Adds a closure to closures that will execute batch in the call combiner.
+  void AddClosureForSubchannelBatch(grpc_call_element* elem,
+                                    grpc_transport_stream_op_batch* batch,
+                                    CallCombinerClosureList* closures);
+  // Adds retriable send_initial_metadata op to batch_data.
+  void AddRetriableSendInitialMetadataOp(SubchannelCallRetryState* retry_state,
+                                         SubchannelCallBatchData* batch_data);
+  // Adds retriable send_message op to batch_data.
+  void AddRetriableSendMessageOp(grpc_call_element* elem,
+                                 SubchannelCallRetryState* retry_state,
+                                 SubchannelCallBatchData* batch_data);
+  // Adds retriable send_trailing_metadata op to batch_data.
+  void AddRetriableSendTrailingMetadataOp(SubchannelCallRetryState* retry_state,
+                                          SubchannelCallBatchData* batch_data);
+  // Adds retriable recv_initial_metadata op to batch_data.
+  void AddRetriableRecvInitialMetadataOp(SubchannelCallRetryState* retry_state,
+                                         SubchannelCallBatchData* batch_data);
+  // Adds retriable recv_message op to batch_data.
+  void AddRetriableRecvMessageOp(SubchannelCallRetryState* retry_state,
+                                 SubchannelCallBatchData* batch_data);
+  // Adds retriable recv_trailing_metadata op to batch_data.
+  void AddRetriableRecvTrailingMetadataOp(SubchannelCallRetryState* retry_state,
+                                          SubchannelCallBatchData* batch_data);
+  // Helper function used to start a recv_trailing_metadata batch.  This
+  // is used in the case where a recv_initial_metadata or recv_message
+  // op fails in a way that we know the call is over but when the application
+  // has not yet started its own recv_trailing_metadata op.
+  void StartInternalRecvTrailingMetadata(grpc_call_element* elem);
+  // If there are any cached send ops that need to be replayed on the
+  // current subchannel call, creates and returns a new subchannel batch
+  // to replay those ops.  Otherwise, returns nullptr.
+  SubchannelCallBatchData* MaybeCreateSubchannelBatchForReplay(
+      grpc_call_element* elem, SubchannelCallRetryState* retry_state);
+  // Adds subchannel batches for pending batches to closures.
+  void AddSubchannelBatchesForPendingBatches(
+      grpc_call_element* elem, SubchannelCallRetryState* retry_state,
+      CallCombinerClosureList* closures);
+  // Constructs and starts whatever subchannel batches are needed on the
+  // subchannel call.
+  static void StartRetriableSubchannelBatches(void* arg, grpc_error* ignored);
+
+  void CreateSubchannelCall(grpc_call_element* elem);
+  // Invoked when a pick is completed, on both success or failure.
+  static void PickDone(void* arg, grpc_error* error);
+  // Removes the call from the channel's list of queued picks.
+  void RemoveCallFromQueuedPicksLocked(grpc_call_element* elem);
+  // Adds the call to the channel's list of queued picks.
+  void AddCallToQueuedPicksLocked(grpc_call_element* elem);
+  // Applies service config to the call.  Must be invoked once we know
+  // that the resolver has returned results to the channel.
+  void ApplyServiceConfigToCallLocked(grpc_call_element* elem);
+
+  // State for handling deadlines.
+  // The code in deadline_filter.c requires this to be the first field.
+  // TODO(roth): This is slightly sub-optimal in that grpc_deadline_state
+  // and this struct both independently store pointers to the call stack
+  // and call combiner.  If/when we have time, find a way to avoid this
+  // without breaking the grpc_deadline_state abstraction.
+  grpc_deadline_state deadline_state_;
+
+  grpc_slice path_;  // Request path.
+  gpr_timespec call_start_time_;
+  grpc_millis deadline_;
+  Arena* arena_;
+  grpc_call_stack* owning_call_;
+  CallCombiner* call_combiner_;
+  grpc_call_context_element* call_context_;
+
+  RefCountedPtr<ServerRetryThrottleData> retry_throttle_data_;
+  ServiceConfig::CallData service_config_call_data_;
+  const ClientChannelMethodParsedObject* method_params_ = nullptr;
+
+  RefCountedPtr<SubchannelCall> subchannel_call_;
+
+  // Set when we get a cancel_stream op.
+  grpc_error* cancel_error_ = GRPC_ERROR_NONE;
+
+  ChannelData::QueuedPick pick_;
+  bool pick_queued_ = false;
+  bool service_config_applied_ = false;
+  QueuedPickCanceller* pick_canceller_ = nullptr;
+  grpc_closure pick_closure_;
+
+  grpc_polling_entity* pollent_ = nullptr;
+
+  // Batches are added to this list when received from above.
+  // They are removed when we are done handling the batch (i.e., when
+  // either we have invoked all of the batch's callbacks or we have
+  // passed the batch down to the subchannel call and are not
+  // intercepting any of its callbacks).
+  PendingBatch pending_batches_[MAX_PENDING_BATCHES] = {};
+  bool pending_send_initial_metadata_ : 1;
+  bool pending_send_message_ : 1;
+  bool pending_send_trailing_metadata_ : 1;
+
+  // Retry state.
+  bool enable_retries_ : 1;
+  bool retry_committed_ : 1;
+  bool last_attempt_got_server_pushback_ : 1;
+  int num_attempts_completed_ = 0;
+  size_t bytes_buffered_for_retry_ = 0;
+  // TODO(roth): Restructure this to eliminate use of ManualConstructor.
+  ManualConstructor<BackOff> retry_backoff_;
+  grpc_timer retry_timer_;
+
+  // The number of pending retriable subchannel batches containing send ops.
+  // We hold a ref to the call stack while this is non-zero, since replay
+  // batches may not complete until after all callbacks have been returned
+  // to the surface, and we need to make sure that the call is not destroyed
+  // until all of these batches have completed.
+  // Note that we actually only need to track replay batches, but it's
+  // easier to track all batches with send ops.
+  int num_pending_retriable_subchannel_send_batches_ = 0;
+
+  // Cached data for retrying send ops.
+  // send_initial_metadata
+  bool seen_send_initial_metadata_ = false;
+  grpc_linked_mdelem* send_initial_metadata_storage_ = nullptr;
+  grpc_metadata_batch send_initial_metadata_;
+  uint32_t send_initial_metadata_flags_;
+  gpr_atm* peer_string_;
+  // send_message
+  // When we get a send_message op, we replace the original byte stream
+  // with a CachingByteStream that caches the slices to a local buffer for
+  // use in retries.
+  // Note: We inline the cache for the first 3 send_message ops and use
+  // dynamic allocation after that.  This number was essentially picked
+  // at random; it could be changed in the future to tune performance.
+  InlinedVector<ByteStreamCache*, 3> send_messages_;
+  // send_trailing_metadata
+  bool seen_send_trailing_metadata_ = false;
+  grpc_linked_mdelem* send_trailing_metadata_storage_ = nullptr;
+  grpc_metadata_batch send_trailing_metadata_;
+};
+
+//
+// ChannelData::ConnectivityStateAndPickerSetter
+//
+
+// A fire-and-forget class that sets the channel's connectivity state
+// and then hops into the data plane combiner to update the picker.
+// Must be instantiated while holding the control plane combiner.
+// Deletes itself when done.
+class ChannelData::ConnectivityStateAndPickerSetter {
+ public:
+  ConnectivityStateAndPickerSetter(
+      ChannelData* chand, grpc_connectivity_state state, const char* reason,
+      UniquePtr<LoadBalancingPolicy::SubchannelPicker> picker)
+      : chand_(chand), picker_(std::move(picker)) {
+    // Update connectivity state here, while holding control plane combiner.
+    grpc_connectivity_state_set(&chand->state_tracker_, state, reason);
+    if (chand->channelz_node_ != nullptr) {
+      chand->channelz_node_->AddTraceEvent(
+          channelz::ChannelTrace::Severity::Info,
+          grpc_slice_from_static_string(
+              GetChannelConnectivityStateChangeString(state)));
+    }
+    // Bounce into the data plane combiner to reset the picker.
+    GRPC_CHANNEL_STACK_REF(chand->owning_stack_,
+                           "ConnectivityStateAndPickerSetter");
+    GRPC_CLOSURE_INIT(&closure_, SetPicker, this,
+                      grpc_combiner_scheduler(chand->data_plane_combiner_));
+    GRPC_CLOSURE_SCHED(&closure_, GRPC_ERROR_NONE);
+  }
+
+ private:
+  static const char* GetChannelConnectivityStateChangeString(
+      grpc_connectivity_state state) {
+    switch (state) {
+      case GRPC_CHANNEL_IDLE:
+        return "Channel state change to IDLE";
+      case GRPC_CHANNEL_CONNECTING:
+        return "Channel state change to CONNECTING";
+      case GRPC_CHANNEL_READY:
+        return "Channel state change to READY";
+      case GRPC_CHANNEL_TRANSIENT_FAILURE:
+        return "Channel state change to TRANSIENT_FAILURE";
+      case GRPC_CHANNEL_SHUTDOWN:
+        return "Channel state change to SHUTDOWN";
+    }
+    GPR_UNREACHABLE_CODE(return "UNKNOWN");
+  }
+
+  static void SetPicker(void* arg, grpc_error* ignored) {
+    auto* self = static_cast<ConnectivityStateAndPickerSetter*>(arg);
+    // Update picker.
+    self->chand_->picker_ = std::move(self->picker_);
+    // Re-process queued picks.
+    for (QueuedPick* pick = self->chand_->queued_picks_; pick != nullptr;
+         pick = pick->next) {
+      CallData::StartPickLocked(pick->elem, GRPC_ERROR_NONE);
+    }
+    // Clean up.
+    GRPC_CHANNEL_STACK_UNREF(self->chand_->owning_stack_,
+                             "ConnectivityStateAndPickerSetter");
+    Delete(self);
+  }
+
+  ChannelData* chand_;
+  UniquePtr<LoadBalancingPolicy::SubchannelPicker> picker_;
+  grpc_closure closure_;
+};
+
+//
+// ChannelData::ServiceConfigSetter
+//
+
+// A fire-and-forget class that sets the channel's service config data
+// in the data plane combiner.  Deletes itself when done.
+class ChannelData::ServiceConfigSetter {
+ public:
+  ServiceConfigSetter(
+      ChannelData* chand,
+      Optional<internal::ClientChannelGlobalParsedObject::RetryThrottling>
+          retry_throttle_data,
+      RefCountedPtr<ServiceConfig> service_config)
+      : chand_(chand),
+        retry_throttle_data_(retry_throttle_data),
+        service_config_(std::move(service_config)) {
+    GRPC_CHANNEL_STACK_REF(chand->owning_stack_, "ServiceConfigSetter");
+    GRPC_CLOSURE_INIT(&closure_, SetServiceConfigData, this,
+                      grpc_combiner_scheduler(chand->data_plane_combiner_));
+    GRPC_CLOSURE_SCHED(&closure_, GRPC_ERROR_NONE);
+  }
+
+ private:
+  static void SetServiceConfigData(void* arg, grpc_error* ignored) {
+    ServiceConfigSetter* self = static_cast<ServiceConfigSetter*>(arg);
+    ChannelData* chand = self->chand_;
+    // Update channel state.
+    chand->received_service_config_data_ = true;
+    if (self->retry_throttle_data_.has_value()) {
+      chand->retry_throttle_data_ =
+          internal::ServerRetryThrottleMap::GetDataForServer(
+              chand->server_name_.get(),
+              self->retry_throttle_data_.value().max_milli_tokens,
+              self->retry_throttle_data_.value().milli_token_ratio);
+    }
+    chand->service_config_ = std::move(self->service_config_);
+    // Apply service config to queued picks.
+    for (QueuedPick* pick = chand->queued_picks_; pick != nullptr;
+         pick = pick->next) {
+      CallData* calld = static_cast<CallData*>(pick->elem->call_data);
+      calld->MaybeApplyServiceConfigToCallLocked(pick->elem);
+    }
+    // Clean up.
+    GRPC_CHANNEL_STACK_UNREF(self->chand_->owning_stack_,
+                             "ServiceConfigSetter");
+    Delete(self);
+  }
+
+  ChannelData* chand_;
+  Optional<internal::ClientChannelGlobalParsedObject::RetryThrottling>
+      retry_throttle_data_;
+  RefCountedPtr<ServiceConfig> service_config_;
+  grpc_closure closure_;
+};
+
+//
+// ChannelData::ExternalConnectivityWatcher::WatcherList
+//
+
+int ChannelData::ExternalConnectivityWatcher::WatcherList::size() const {
+  MutexLock lock(&mu_);
+  int count = 0;
+  for (ExternalConnectivityWatcher* w = head_; w != nullptr; w = w->next_) {
+    ++count;
+  }
+  return count;
+}
+
+ChannelData::ExternalConnectivityWatcher*
+ChannelData::ExternalConnectivityWatcher::WatcherList::Lookup(
+    grpc_closure* on_complete) const {
+  MutexLock lock(&mu_);
+  ExternalConnectivityWatcher* w = head_;
+  while (w != nullptr && w->on_complete_ != on_complete) {
+    w = w->next_;
+  }
+  return w;
+}
+
+void ChannelData::ExternalConnectivityWatcher::WatcherList::Add(
+    ExternalConnectivityWatcher* watcher) {
+  GPR_ASSERT(Lookup(watcher->on_complete_) == nullptr);
+  MutexLock lock(&mu_);
+  GPR_ASSERT(watcher->next_ == nullptr);
+  watcher->next_ = head_;
+  head_ = watcher;
+}
+
+void ChannelData::ExternalConnectivityWatcher::WatcherList::Remove(
+    const ExternalConnectivityWatcher* watcher) {
+  MutexLock lock(&mu_);
+  if (watcher == head_) {
+    head_ = watcher->next_;
+    return;
+  }
+  for (ExternalConnectivityWatcher* w = head_; w != nullptr; w = w->next_) {
+    if (w->next_ == watcher) {
+      w->next_ = w->next_->next_;
+      return;
+    }
+  }
+  GPR_UNREACHABLE_CODE(return );
+}
+
+//
+// ChannelData::ExternalConnectivityWatcher
+//
+
+ChannelData::ExternalConnectivityWatcher::ExternalConnectivityWatcher(
+    ChannelData* chand, grpc_polling_entity pollent,
+    grpc_connectivity_state* state, grpc_closure* on_complete,
+    grpc_closure* watcher_timer_init)
+    : chand_(chand),
+      pollent_(pollent),
+      state_(state),
+      on_complete_(on_complete),
+      watcher_timer_init_(watcher_timer_init) {
+  grpc_polling_entity_add_to_pollset_set(&pollent_,
+                                         chand_->interested_parties_);
+  GRPC_CHANNEL_STACK_REF(chand_->owning_stack_, "ExternalConnectivityWatcher");
+  GRPC_CLOSURE_SCHED(
+      GRPC_CLOSURE_INIT(&my_closure_, WatchConnectivityStateLocked, this,
+                        grpc_combiner_scheduler(chand_->combiner_)),
+      GRPC_ERROR_NONE);
+}
+
+ChannelData::ExternalConnectivityWatcher::~ExternalConnectivityWatcher() {
+  grpc_polling_entity_del_from_pollset_set(&pollent_,
+                                           chand_->interested_parties_);
+  GRPC_CHANNEL_STACK_UNREF(chand_->owning_stack_,
+                           "ExternalConnectivityWatcher");
+}
+
+void ChannelData::ExternalConnectivityWatcher::OnWatchCompleteLocked(
+    void* arg, grpc_error* error) {
+  ExternalConnectivityWatcher* self =
+      static_cast<ExternalConnectivityWatcher*>(arg);
+  grpc_closure* on_complete = self->on_complete_;
+  self->chand_->external_connectivity_watcher_list_.Remove(self);
+  Delete(self);
+  GRPC_CLOSURE_SCHED(on_complete, GRPC_ERROR_REF(error));
+}
+
+void ChannelData::ExternalConnectivityWatcher::WatchConnectivityStateLocked(
+    void* arg, grpc_error* ignored) {
+  ExternalConnectivityWatcher* self =
+      static_cast<ExternalConnectivityWatcher*>(arg);
+  if (self->state_ == nullptr) {
+    // Handle cancellation.
+    GPR_ASSERT(self->watcher_timer_init_ == nullptr);
+    ExternalConnectivityWatcher* found =
+        self->chand_->external_connectivity_watcher_list_.Lookup(
+            self->on_complete_);
+    if (found != nullptr) {
+      grpc_connectivity_state_notify_on_state_change(
+          &found->chand_->state_tracker_, nullptr, &found->my_closure_);
+    }
+    Delete(self);
+    return;
+  }
+  // New watcher.
+  self->chand_->external_connectivity_watcher_list_.Add(self);
+  // This assumes that the closure is scheduled on the ExecCtx scheduler
+  // and that GRPC_CLOSURE_RUN would run the closure immediately.
+  GRPC_CLOSURE_RUN(self->watcher_timer_init_, GRPC_ERROR_NONE);
+  GRPC_CLOSURE_INIT(&self->my_closure_, OnWatchCompleteLocked, self,
+                    grpc_combiner_scheduler(self->chand_->combiner_));
+  grpc_connectivity_state_notify_on_state_change(
+      &self->chand_->state_tracker_, self->state_, &self->my_closure_);
+}
+
+//
+// ChannelData::ClientChannelControlHelper
+//
+
+class ChannelData::ClientChannelControlHelper
     : public LoadBalancingPolicy::ChannelControlHelper {
  public:
-  explicit ClientChannelControlHelper(channel_data* chand) : chand_(chand) {
-    GRPC_CHANNEL_STACK_REF(chand_->owning_stack, "ClientChannelControlHelper");
+  explicit ClientChannelControlHelper(ChannelData* chand) : chand_(chand) {
+    GRPC_CHANNEL_STACK_REF(chand_->owning_stack_, "ClientChannelControlHelper");
   }
 
   ~ClientChannelControlHelper() override {
-    GRPC_CHANNEL_STACK_UNREF(chand_->owning_stack,
+    GRPC_CHANNEL_STACK_UNREF(chand_->owning_stack_,
                              "ClientChannelControlHelper");
   }
 
   Subchannel* CreateSubchannel(const grpc_channel_args& args) override {
-    grpc_arg arg = SubchannelPoolInterface::CreateChannelArg(
-        chand_->subchannel_pool.get());
+    grpc_arg args_to_add[2];
+    int num_args_to_add = 0;
+    if (chand_->health_check_service_name_ != nullptr) {
+      args_to_add[0] = grpc_channel_arg_string_create(
+          const_cast<char*>("grpc.temp.health_check"),
+          const_cast<char*>(chand_->health_check_service_name_.get()));
+      num_args_to_add++;
+    }
+    args_to_add[num_args_to_add++] = SubchannelPoolInterface::CreateChannelArg(
+        chand_->subchannel_pool_.get());
     grpc_channel_args* new_args =
-        grpc_channel_args_copy_and_add(&args, &arg, 1);
+        grpc_channel_args_copy_and_add(&args, args_to_add, num_args_to_add);
     Subchannel* subchannel =
-        chand_->client_channel_factory->CreateSubchannel(new_args);
+        chand_->client_channel_factory_->CreateSubchannel(new_args);
     grpc_channel_args_destroy(new_args);
     return subchannel;
   }
 
   grpc_channel* CreateChannel(const char* target,
                               const grpc_channel_args& args) override {
-    return chand_->client_channel_factory->CreateChannel(target, &args);
+    return chand_->client_channel_factory_->CreateChannel(target, &args);
   }
 
   void UpdateState(
-      grpc_connectivity_state state, grpc_error* state_error,
+      grpc_connectivity_state state,
       UniquePtr<LoadBalancingPolicy::SubchannelPicker> picker) override {
-    if (grpc_client_channel_routing_trace.enabled()) {
-      const char* extra = chand_->disconnect_error == GRPC_ERROR_NONE
+    grpc_error* disconnect_error =
+        chand_->disconnect_error_.Load(MemoryOrder::ACQUIRE);
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
+      const char* extra = disconnect_error == GRPC_ERROR_NONE
                               ? ""
                               : " (ignoring -- channel shutting down)";
-      gpr_log(GPR_INFO, "chand=%p: update: state=%s error=%s picker=%p%s",
-              chand_, grpc_connectivity_state_name(state),
-              grpc_error_string(state_error), picker.get(), extra);
+      gpr_log(GPR_INFO, "chand=%p: update: state=%s picker=%p%s", chand_,
+              grpc_connectivity_state_name(state), picker.get(), extra);
     }
     // Do update only if not shutting down.
-    if (chand_->disconnect_error == GRPC_ERROR_NONE) {
-      set_connectivity_state_and_picker_locked(chand_, state, state_error,
-                                               "helper", std::move(picker));
-    } else {
-      GRPC_ERROR_UNREF(state_error);
+    if (disconnect_error == GRPC_ERROR_NONE) {
+      // Will delete itself.
+      New<ConnectivityStateAndPickerSetter>(chand_, state, "helper",
+                                            std::move(picker));
     }
   }
 
@@ -243,63 +998,326 @@
   void RequestReresolution() override {}
 
  private:
-  channel_data* chand_;
+  ChannelData* chand_;
 };
 
-}  // namespace
-}  // namespace grpc_core
+//
+// ChannelData implementation
+//
 
-// Synchronous callback from chand->resolving_lb_policy to process a resolver
-// result update.
-static bool process_resolver_result_locked(
-    void* arg, grpc_core::Resolver::Result* result, const char** lb_policy_name,
-    grpc_core::RefCountedPtr<LoadBalancingPolicy::Config>* lb_policy_config) {
-  channel_data* chand = static_cast<channel_data*>(arg);
-  chand->have_service_config = true;
-  ProcessedResolverResult resolver_result(result, chand->enable_retries);
-  grpc_core::UniquePtr<char> service_config_json =
-      resolver_result.service_config_json();
-  if (grpc_client_channel_routing_trace.enabled()) {
-    gpr_log(GPR_INFO, "chand=%p: resolver returned service config: \"%s\"",
-            chand, service_config_json.get());
+grpc_error* ChannelData::Init(grpc_channel_element* elem,
+                              grpc_channel_element_args* args) {
+  GPR_ASSERT(args->is_last);
+  GPR_ASSERT(elem->filter == &grpc_client_channel_filter);
+  grpc_error* error = GRPC_ERROR_NONE;
+  new (elem->channel_data) ChannelData(args, &error);
+  return error;
+}
+
+void ChannelData::Destroy(grpc_channel_element* elem) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  chand->~ChannelData();
+}
+
+bool GetEnableRetries(const grpc_channel_args* args) {
+  return grpc_channel_arg_get_bool(
+      grpc_channel_args_find(args, GRPC_ARG_ENABLE_RETRIES), true);
+}
+
+size_t GetMaxPerRpcRetryBufferSize(const grpc_channel_args* args) {
+  return static_cast<size_t>(grpc_channel_arg_get_integer(
+      grpc_channel_args_find(args, GRPC_ARG_PER_RPC_RETRY_BUFFER_SIZE),
+      {DEFAULT_PER_RPC_RETRY_BUFFER_SIZE, 0, INT_MAX}));
+}
+
+RefCountedPtr<SubchannelPoolInterface> GetSubchannelPool(
+    const grpc_channel_args* args) {
+  const bool use_local_subchannel_pool = grpc_channel_arg_get_bool(
+      grpc_channel_args_find(args, GRPC_ARG_USE_LOCAL_SUBCHANNEL_POOL), false);
+  if (use_local_subchannel_pool) {
+    return MakeRefCounted<LocalSubchannelPool>();
   }
-  // Update channel state.
-  chand->retry_throttle_data = resolver_result.retry_throttle_data();
-  chand->method_params_table = resolver_result.method_params_table();
-  // Swap out the data used by cc_get_channel_info().
-  gpr_mu_lock(&chand->info_mu);
-  chand->info_lb_policy_name = resolver_result.lb_policy_name();
+  return GlobalSubchannelPool::instance();
+}
+
+ChannelData::ChannelData(grpc_channel_element_args* args, grpc_error** error)
+    : deadline_checking_enabled_(
+          grpc_deadline_checking_enabled(args->channel_args)),
+      enable_retries_(GetEnableRetries(args->channel_args)),
+      per_rpc_retry_buffer_size_(
+          GetMaxPerRpcRetryBufferSize(args->channel_args)),
+      owning_stack_(args->channel_stack),
+      client_channel_factory_(
+          ClientChannelFactory::GetFromChannelArgs(args->channel_args)),
+      data_plane_combiner_(grpc_combiner_create()),
+      combiner_(grpc_combiner_create()),
+      interested_parties_(grpc_pollset_set_create()),
+      subchannel_pool_(GetSubchannelPool(args->channel_args)),
+      disconnect_error_(GRPC_ERROR_NONE) {
+  // Initialize data members.
+  grpc_connectivity_state_init(&state_tracker_, GRPC_CHANNEL_IDLE,
+                               "client_channel");
+  gpr_mu_init(&info_mu_);
+  // Start backup polling.
+  grpc_client_channel_start_backup_polling(interested_parties_);
+  // Check client channel factory.
+  if (client_channel_factory_ == nullptr) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "Missing client channel factory in args for client channel filter");
+    return;
+  }
+  // Get server name to resolve, using proxy mapper if needed.
+  const char* server_uri = grpc_channel_arg_get_string(
+      grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVER_URI));
+  if (server_uri == nullptr) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "server URI channel arg missing or wrong type in client channel "
+        "filter");
+    return;
+  }
+  // Get default service config
+  const char* service_config_json = grpc_channel_arg_get_string(
+      grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVICE_CONFIG));
+  // TODO(yashkt): Make sure we set the channel in TRANSIENT_FAILURE on an
+  // invalid default service config
+  if (service_config_json != nullptr) {
+    *error = GRPC_ERROR_NONE;
+    default_service_config_ = ServiceConfig::Create(service_config_json, error);
+    if (*error != GRPC_ERROR_NONE) {
+      default_service_config_.reset();
+      return;
+    }
+  }
+  grpc_uri* uri = grpc_uri_parse(server_uri, true);
+  if (uri != nullptr && uri->path[0] != '\0') {
+    server_name_.reset(
+        gpr_strdup(uri->path[0] == '/' ? uri->path + 1 : uri->path));
+  }
+  grpc_uri_destroy(uri);
+  char* proxy_name = nullptr;
+  grpc_channel_args* new_args = nullptr;
+  grpc_proxy_mappers_map_name(server_uri, args->channel_args, &proxy_name,
+                              &new_args);
+  UniquePtr<char> target_uri(proxy_name != nullptr ? proxy_name
+                                                   : gpr_strdup(server_uri));
+  // Instantiate resolving LB policy.
+  LoadBalancingPolicy::Args lb_args;
+  lb_args.combiner = combiner_;
+  lb_args.channel_control_helper =
+      UniquePtr<LoadBalancingPolicy::ChannelControlHelper>(
+          New<ClientChannelControlHelper>(this));
+  lb_args.args = new_args != nullptr ? new_args : args->channel_args;
+  resolving_lb_policy_.reset(New<ResolvingLoadBalancingPolicy>(
+      std::move(lb_args), &grpc_client_channel_routing_trace,
+      std::move(target_uri), ProcessResolverResultLocked, this, error));
+  grpc_channel_args_destroy(new_args);
+  if (*error != GRPC_ERROR_NONE) {
+    // Orphan the resolving LB policy and flush the exec_ctx to ensure
+    // that it finishes shutting down.  This ensures that if we are
+    // failing, we destroy the ClientChannelControlHelper (and thus
+    // unref the channel stack) before we return.
+    // TODO(roth): This is not a complete solution, because it only
+    // catches the case where channel stack initialization fails in this
+    // particular filter.  If there is a failure in a different filter, we
+    // will leave a dangling ref here, which can cause a crash.  Fortunately,
+    // in practice, there are no other filters that can cause failures in
+    // channel stack initialization, so this works for now.
+    resolving_lb_policy_.reset();
+    ExecCtx::Get()->Flush();
+  } else {
+    grpc_pollset_set_add_pollset_set(resolving_lb_policy_->interested_parties(),
+                                     interested_parties_);
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
+      gpr_log(GPR_INFO, "chand=%p: created resolving_lb_policy=%p", this,
+              resolving_lb_policy_.get());
+    }
+  }
+}
+
+ChannelData::~ChannelData() {
+  if (resolving_lb_policy_ != nullptr) {
+    grpc_pollset_set_del_pollset_set(resolving_lb_policy_->interested_parties(),
+                                     interested_parties_);
+    resolving_lb_policy_.reset();
+  }
+  // Stop backup polling.
+  grpc_client_channel_stop_backup_polling(interested_parties_);
+  grpc_pollset_set_destroy(interested_parties_);
+  GRPC_COMBINER_UNREF(data_plane_combiner_, "client_channel");
+  GRPC_COMBINER_UNREF(combiner_, "client_channel");
+  GRPC_ERROR_UNREF(disconnect_error_.Load(MemoryOrder::RELAXED));
+  grpc_connectivity_state_destroy(&state_tracker_);
+  gpr_mu_destroy(&info_mu_);
+}
+
+void ChannelData::ProcessLbPolicy(
+    const Resolver::Result& resolver_result,
+    const internal::ClientChannelGlobalParsedObject* parsed_service_config,
+    UniquePtr<char>* lb_policy_name,
+    RefCountedPtr<ParsedLoadBalancingConfig>* lb_policy_config) {
+  // Prefer the LB policy name found in the service config.
+  if (parsed_service_config != nullptr &&
+      parsed_service_config->parsed_lb_config() != nullptr) {
+    lb_policy_name->reset(
+        gpr_strdup(parsed_service_config->parsed_lb_config()->name()));
+    *lb_policy_config = parsed_service_config->parsed_lb_config();
+    return;
+  }
+  const char* local_policy_name = nullptr;
+  if (parsed_service_config != nullptr &&
+      parsed_service_config->parsed_deprecated_lb_policy() != nullptr) {
+    local_policy_name = parsed_service_config->parsed_deprecated_lb_policy();
+  } else {
+    const grpc_arg* channel_arg =
+        grpc_channel_args_find(resolver_result.args, GRPC_ARG_LB_POLICY_NAME);
+    local_policy_name = grpc_channel_arg_get_string(channel_arg);
+  }
+  // Special case: If at least one balancer address is present, we use
+  // the grpclb policy, regardless of what the resolver has returned.
+  bool found_balancer_address = false;
+  for (size_t i = 0; i < resolver_result.addresses.size(); ++i) {
+    const ServerAddress& address = resolver_result.addresses[i];
+    if (address.IsBalancer()) {
+      found_balancer_address = true;
+      break;
+    }
+  }
+  if (found_balancer_address) {
+    if (local_policy_name != nullptr &&
+        strcmp(local_policy_name, "grpclb") != 0) {
+      gpr_log(GPR_INFO,
+              "resolver requested LB policy %s but provided at least one "
+              "balancer address -- forcing use of grpclb LB policy",
+              local_policy_name);
+    }
+    local_policy_name = "grpclb";
+  }
+  // Use pick_first if nothing was specified and we didn't select grpclb
+  // above.
+  lb_policy_name->reset(gpr_strdup(
+      local_policy_name == nullptr ? "pick_first" : local_policy_name));
+}
+
+// Synchronous callback from ResolvingLoadBalancingPolicy to process a
+// resolver result update.
+bool ChannelData::ProcessResolverResultLocked(
+    void* arg, const Resolver::Result& result, const char** lb_policy_name,
+    RefCountedPtr<ParsedLoadBalancingConfig>* lb_policy_config,
+    grpc_error** service_config_error) {
+  ChannelData* chand = static_cast<ChannelData*>(arg);
+  RefCountedPtr<ServiceConfig> service_config;
+  // If resolver did not return a service config or returned an invalid service
+  // config, we need a fallback service config.
+  if (result.service_config_error != GRPC_ERROR_NONE) {
+    // If the service config was invalid, then fallback to the saved service
+    // config. If there is no saved config either, use the default service
+    // config.
+    if (chand->saved_service_config_ != nullptr) {
+      service_config = chand->saved_service_config_;
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
+        gpr_log(GPR_INFO,
+                "chand=%p: resolver returned invalid service config. "
+                "Continuing to use previous service config.",
+                chand);
+      }
+    } else if (chand->default_service_config_ != nullptr) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
+        gpr_log(GPR_INFO,
+                "chand=%p: resolver returned invalid service config. Using "
+                "default service config provided by client API.",
+                chand);
+      }
+      service_config = chand->default_service_config_;
+    }
+  } else if (result.service_config == nullptr) {
+    if (chand->default_service_config_ != nullptr) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
+        gpr_log(GPR_INFO,
+                "chand=%p: resolver returned no service config. Using default "
+                "service config provided by client API.",
+                chand);
+      }
+      service_config = chand->default_service_config_;
+    }
+  } else {
+    service_config = result.service_config;
+  }
+  *service_config_error = GRPC_ERROR_REF(result.service_config_error);
+  if (service_config == nullptr &&
+      result.service_config_error != GRPC_ERROR_NONE) {
+    return false;
+  }
+  UniquePtr<char> service_config_json;
+  // Process service config.
+  const internal::ClientChannelGlobalParsedObject* parsed_service_config =
+      nullptr;
+  if (service_config != nullptr) {
+    parsed_service_config =
+        static_cast<const internal::ClientChannelGlobalParsedObject*>(
+            service_config->GetParsedGlobalServiceConfigObject(
+                internal::ClientChannelServiceConfigParser::ParserIndex()));
+  }
   const bool service_config_changed =
-      ((service_config_json == nullptr) !=
-       (chand->info_service_config_json == nullptr)) ||
-      (service_config_json != nullptr &&
-       strcmp(service_config_json.get(),
-              chand->info_service_config_json.get()) != 0);
-  chand->info_service_config_json = std::move(service_config_json);
-  gpr_mu_unlock(&chand->info_mu);
-  // Return results.
-  *lb_policy_name = chand->info_lb_policy_name.get();
-  *lb_policy_config = resolver_result.lb_policy_config();
-  // Apply service config to queued picks.
-  for (QueuedPick* pick = chand->queued_picks; pick != nullptr;
-       pick = pick->next) {
-    maybe_apply_service_config_to_call_locked(pick->elem);
+      ((service_config == nullptr) !=
+       (chand->saved_service_config_ == nullptr)) ||
+      (service_config != nullptr &&
+       strcmp(service_config->service_config_json(),
+              chand->saved_service_config_->service_config_json()) != 0);
+  if (service_config_changed) {
+    service_config_json.reset(gpr_strdup(
+        service_config != nullptr ? service_config->service_config_json()
+                                  : ""));
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
+      gpr_log(GPR_INFO,
+              "chand=%p: resolver returned updated service config: \"%s\"",
+              chand, service_config_json.get());
+    }
+    chand->saved_service_config_ = std::move(service_config);
+    if (parsed_service_config != nullptr) {
+      chand->health_check_service_name_.reset(
+          gpr_strdup(parsed_service_config->health_check_service_name()));
+    } else {
+      chand->health_check_service_name_.reset();
+    }
   }
+  // We want to set the service config at least once. This should not really be
+  // needed, but we are doing it as a defensive approach. This can be removed,
+  // if we feel it is unnecessary.
+  if (service_config_changed || !chand->received_first_resolver_result_) {
+    chand->received_first_resolver_result_ = true;
+    Optional<internal::ClientChannelGlobalParsedObject::RetryThrottling>
+        retry_throttle_data;
+    if (parsed_service_config != nullptr) {
+      retry_throttle_data = parsed_service_config->retry_throttling();
+    }
+    // Create service config setter to update channel state in the data
+    // plane combiner.  Destroys itself when done.
+    New<ServiceConfigSetter>(chand, retry_throttle_data,
+                             chand->saved_service_config_);
+  }
+  UniquePtr<char> processed_lb_policy_name;
+  chand->ProcessLbPolicy(result, parsed_service_config,
+                         &processed_lb_policy_name, lb_policy_config);
+  // Swap out the data used by GetChannelInfo().
+  {
+    MutexLock lock(&chand->info_mu_);
+    chand->info_lb_policy_name_ = std::move(processed_lb_policy_name);
+    if (service_config_json != nullptr) {
+      chand->info_service_config_json_ = std::move(service_config_json);
+    }
+  }
+  // Return results.
+  *lb_policy_name = chand->info_lb_policy_name_.get();
   return service_config_changed;
 }
 
-static grpc_error* do_ping_locked(channel_data* chand, grpc_transport_op* op) {
-  grpc_error* error = GRPC_ERROR_NONE;
-  grpc_connectivity_state state =
-      grpc_connectivity_state_get(&chand->state_tracker, &error);
-  if (state != GRPC_CHANNEL_READY) {
-    grpc_error* new_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
-        "channel not connected", &error, 1);
-    GRPC_ERROR_UNREF(error);
-    return new_error;
+grpc_error* ChannelData::DoPingLocked(grpc_transport_op* op) {
+  if (grpc_connectivity_state_check(&state_tracker_) != GRPC_CHANNEL_READY) {
+    return GRPC_ERROR_CREATE_FROM_STATIC_STRING("channel not connected");
   }
   LoadBalancingPolicy::PickArgs pick;
-  chand->picker->Pick(&pick, &error);
+  grpc_error* error = GRPC_ERROR_NONE;
+  picker_->Pick(&pick, &error);
   if (pick.connected_subchannel != nullptr) {
     pick.connected_subchannel->Ping(op->send_ping.on_initiate,
                                     op->send_ping.on_ack);
@@ -312,22 +1330,22 @@
   return error;
 }
 
-static void start_transport_op_locked(void* arg, grpc_error* error_ignored) {
+void ChannelData::StartTransportOpLocked(void* arg, grpc_error* ignored) {
   grpc_transport_op* op = static_cast<grpc_transport_op*>(arg);
   grpc_channel_element* elem =
       static_cast<grpc_channel_element*>(op->handler_private.extra_arg);
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  // Connectivity watch.
   if (op->on_connectivity_state_change != nullptr) {
     grpc_connectivity_state_notify_on_state_change(
-        &chand->state_tracker, op->connectivity_state,
+        &chand->state_tracker_, op->connectivity_state,
         op->on_connectivity_state_change);
     op->on_connectivity_state_change = nullptr;
     op->connectivity_state = nullptr;
   }
-
+  // Ping.
   if (op->send_ping.on_initiate != nullptr || op->send_ping.on_ack != nullptr) {
-    grpc_error* error = do_ping_locked(chand, op);
+    grpc_error* error = chand->DoPingLocked(op);
     if (error != GRPC_ERROR_NONE) {
       GRPC_CLOSURE_SCHED(op->send_ping.on_initiate, GRPC_ERROR_REF(error));
       GRPC_CLOSURE_SCHED(op->send_ping.on_ack, error);
@@ -336,201 +1354,111 @@
     op->send_ping.on_initiate = nullptr;
     op->send_ping.on_ack = nullptr;
   }
-
+  // Reset backoff.
   if (op->reset_connect_backoff) {
-    chand->resolving_lb_policy->ResetBackoffLocked();
+    if (chand->resolving_lb_policy_ != nullptr) {
+      chand->resolving_lb_policy_->ResetBackoffLocked();
+    }
   }
-
+  // Disconnect.
   if (op->disconnect_with_error != GRPC_ERROR_NONE) {
-    chand->disconnect_error = op->disconnect_with_error;
+    grpc_error* error = GRPC_ERROR_NONE;
+    GPR_ASSERT(chand->disconnect_error_.CompareExchangeStrong(
+        &error, op->disconnect_with_error, MemoryOrder::ACQ_REL,
+        MemoryOrder::ACQUIRE));
     grpc_pollset_set_del_pollset_set(
-        chand->resolving_lb_policy->interested_parties(),
-        chand->interested_parties);
-    chand->resolving_lb_policy.reset();
-    set_connectivity_state_and_picker_locked(
-        chand, GRPC_CHANNEL_SHUTDOWN, GRPC_ERROR_REF(op->disconnect_with_error),
-        "shutdown from API",
-        grpc_core::UniquePtr<LoadBalancingPolicy::SubchannelPicker>(
-            grpc_core::New<LoadBalancingPolicy::TransientFailurePicker>(
+        chand->resolving_lb_policy_->interested_parties(),
+        chand->interested_parties_);
+    chand->resolving_lb_policy_.reset();
+    // Will delete itself.
+    New<ConnectivityStateAndPickerSetter>(
+        chand, GRPC_CHANNEL_SHUTDOWN, "shutdown from API",
+        UniquePtr<LoadBalancingPolicy::SubchannelPicker>(
+            New<LoadBalancingPolicy::TransientFailurePicker>(
                 GRPC_ERROR_REF(op->disconnect_with_error))));
   }
-
-  GRPC_CHANNEL_STACK_UNREF(chand->owning_stack, "start_transport_op");
+  GRPC_CHANNEL_STACK_UNREF(chand->owning_stack_, "start_transport_op");
   GRPC_CLOSURE_SCHED(op->on_consumed, GRPC_ERROR_NONE);
 }
 
-static void cc_start_transport_op(grpc_channel_element* elem,
-                                  grpc_transport_op* op) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-
+void ChannelData::StartTransportOp(grpc_channel_element* elem,
+                                   grpc_transport_op* op) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   GPR_ASSERT(op->set_accept_stream == false);
+  // Handle bind_pollset.
   if (op->bind_pollset != nullptr) {
-    grpc_pollset_set_add_pollset(chand->interested_parties, op->bind_pollset);
+    grpc_pollset_set_add_pollset(chand->interested_parties_, op->bind_pollset);
   }
-
+  // Pop into control plane combiner for remaining ops.
   op->handler_private.extra_arg = elem;
-  GRPC_CHANNEL_STACK_REF(chand->owning_stack, "start_transport_op");
+  GRPC_CHANNEL_STACK_REF(chand->owning_stack_, "start_transport_op");
   GRPC_CLOSURE_SCHED(
-      GRPC_CLOSURE_INIT(&op->handler_private.closure, start_transport_op_locked,
-                        op, grpc_combiner_scheduler(chand->combiner)),
+      GRPC_CLOSURE_INIT(&op->handler_private.closure,
+                        ChannelData::StartTransportOpLocked, op,
+                        grpc_combiner_scheduler(chand->combiner_)),
       GRPC_ERROR_NONE);
 }
 
-static void cc_get_channel_info(grpc_channel_element* elem,
-                                const grpc_channel_info* info) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  gpr_mu_lock(&chand->info_mu);
+void ChannelData::GetChannelInfo(grpc_channel_element* elem,
+                                 const grpc_channel_info* info) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  MutexLock lock(&chand->info_mu_);
   if (info->lb_policy_name != nullptr) {
-    *info->lb_policy_name = gpr_strdup(chand->info_lb_policy_name.get());
+    *info->lb_policy_name = gpr_strdup(chand->info_lb_policy_name_.get());
   }
   if (info->service_config_json != nullptr) {
     *info->service_config_json =
-        gpr_strdup(chand->info_service_config_json.get());
+        gpr_strdup(chand->info_service_config_json_.get());
   }
-  gpr_mu_unlock(&chand->info_mu);
 }
 
-/* Constructor for channel_data */
-static grpc_error* cc_init_channel_elem(grpc_channel_element* elem,
-                                        grpc_channel_element_args* args) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  GPR_ASSERT(args->is_last);
-  GPR_ASSERT(elem->filter == &grpc_client_channel_filter);
-  // Initialize data members.
-  chand->combiner = grpc_combiner_create();
-  grpc_connectivity_state_init(&chand->state_tracker, GRPC_CHANNEL_IDLE,
-                               "client_channel");
-  chand->disconnect_error = GRPC_ERROR_NONE;
-  gpr_mu_init(&chand->info_mu);
-  gpr_mu_init(&chand->external_connectivity_watcher_list_mu);
+void ChannelData::AddQueuedPick(QueuedPick* pick,
+                                grpc_polling_entity* pollent) {
+  // Add call to queued picks list.
+  pick->next = queued_picks_;
+  queued_picks_ = pick;
+  // Add call's pollent to channel's interested_parties, so that I/O
+  // can be done under the call's CQ.
+  grpc_polling_entity_add_to_pollset_set(pollent, interested_parties_);
+}
 
-  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
-  chand->external_connectivity_watcher_list_head = nullptr;
-  gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
-
-  chand->owning_stack = args->channel_stack;
-  chand->deadline_checking_enabled =
-      grpc_deadline_checking_enabled(args->channel_args);
-  chand->interested_parties = grpc_pollset_set_create();
-  grpc_client_channel_start_backup_polling(chand->interested_parties);
-  // Record max per-RPC retry buffer size.
-  const grpc_arg* arg = grpc_channel_args_find(
-      args->channel_args, GRPC_ARG_PER_RPC_RETRY_BUFFER_SIZE);
-  chand->per_rpc_retry_buffer_size = (size_t)grpc_channel_arg_get_integer(
-      arg, {DEFAULT_PER_RPC_RETRY_BUFFER_SIZE, 0, INT_MAX});
-  // Record enable_retries.
-  arg = grpc_channel_args_find(args->channel_args, GRPC_ARG_ENABLE_RETRIES);
-  chand->enable_retries = grpc_channel_arg_get_bool(arg, true);
-  // Record client channel factory.
-  chand->client_channel_factory =
-      grpc_core::ClientChannelFactory::GetFromChannelArgs(args->channel_args);
-  if (chand->client_channel_factory == nullptr) {
-    return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
-        "Missing client channel factory in args for client channel filter");
-  }
-  // Get server name to resolve, using proxy mapper if needed.
-  arg = grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVER_URI);
-  if (arg == nullptr) {
-    return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
-        "Missing server uri in args for client channel filter");
-  }
-  if (arg->type != GRPC_ARG_STRING) {
-    return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
-        "server uri arg must be a string");
-  }
-  char* proxy_name = nullptr;
-  grpc_channel_args* new_args = nullptr;
-  grpc_proxy_mappers_map_name(arg->value.string, args->channel_args,
-                              &proxy_name, &new_args);
-  grpc_core::UniquePtr<char> target_uri(
-      proxy_name != nullptr ? proxy_name : gpr_strdup(arg->value.string));
-  // Instantiate subchannel pool.
-  arg = grpc_channel_args_find(args->channel_args,
-                               GRPC_ARG_USE_LOCAL_SUBCHANNEL_POOL);
-  if (grpc_channel_arg_get_bool(arg, false)) {
-    chand->subchannel_pool =
-        grpc_core::MakeRefCounted<grpc_core::LocalSubchannelPool>();
-  } else {
-    chand->subchannel_pool = grpc_core::GlobalSubchannelPool::instance();
-  }
-  // Instantiate resolving LB policy.
-  LoadBalancingPolicy::Args lb_args;
-  lb_args.combiner = chand->combiner;
-  lb_args.channel_control_helper =
-      grpc_core::UniquePtr<LoadBalancingPolicy::ChannelControlHelper>(
-          grpc_core::New<grpc_core::ClientChannelControlHelper>(chand));
-  lb_args.args = new_args != nullptr ? new_args : args->channel_args;
-  grpc_error* error = GRPC_ERROR_NONE;
-  chand->resolving_lb_policy.reset(
-      grpc_core::New<grpc_core::ResolvingLoadBalancingPolicy>(
-          std::move(lb_args), &grpc_client_channel_routing_trace,
-          std::move(target_uri), process_resolver_result_locked, chand,
-          &error));
-  grpc_channel_args_destroy(new_args);
-  if (error != GRPC_ERROR_NONE) {
-    // Orphan the resolving LB policy and flush the exec_ctx to ensure
-    // that it finishes shutting down.  This ensures that if we are
-    // failing, we destroy the ClientChannelControlHelper (and thus
-    // unref the channel stack) before we return.
-    // TODO(roth): This is not a complete solution, because it only
-    // catches the case where channel stack initialization fails in this
-    // particular filter.  If there is a failure in a different filter, we
-    // will leave a dangling ref here, which can cause a crash.  Fortunately,
-    // in practice, there are no other filters that can cause failures in
-    // channel stack initialization, so this works for now.
-    chand->resolving_lb_policy.reset();
-    grpc_core::ExecCtx::Get()->Flush();
-  } else {
-    grpc_pollset_set_add_pollset_set(
-        chand->resolving_lb_policy->interested_parties(),
-        chand->interested_parties);
-    if (grpc_client_channel_routing_trace.enabled()) {
-      gpr_log(GPR_INFO, "chand=%p: created resolving_lb_policy=%p", chand,
-              chand->resolving_lb_policy.get());
+void ChannelData::RemoveQueuedPick(QueuedPick* to_remove,
+                                   grpc_polling_entity* pollent) {
+  // Remove call's pollent from channel's interested_parties.
+  grpc_polling_entity_del_from_pollset_set(pollent, interested_parties_);
+  // Remove from queued picks list.
+  for (QueuedPick** pick = &queued_picks_; *pick != nullptr;
+       pick = &(*pick)->next) {
+    if (*pick == to_remove) {
+      *pick = to_remove->next;
+      return;
     }
   }
-  return error;
 }
 
-/* Destructor for channel_data */
-static void cc_destroy_channel_elem(grpc_channel_element* elem) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  if (chand->resolving_lb_policy != nullptr) {
-    grpc_pollset_set_del_pollset_set(
-        chand->resolving_lb_policy->interested_parties(),
-        chand->interested_parties);
-    chand->resolving_lb_policy.reset();
+void ChannelData::TryToConnectLocked(void* arg, grpc_error* error_ignored) {
+  auto* chand = static_cast<ChannelData*>(arg);
+  if (chand->resolving_lb_policy_ != nullptr) {
+    chand->resolving_lb_policy_->ExitIdleLocked();
   }
-  // TODO(roth): Once we convert the filter API to C++, there will no
-  // longer be any need to explicitly reset these smart pointer data members.
-  chand->picker.reset();
-  chand->subchannel_pool.reset();
-  chand->info_lb_policy_name.reset();
-  chand->info_service_config_json.reset();
-  chand->retry_throttle_data.reset();
-  chand->method_params_table.reset();
-  grpc_client_channel_stop_backup_polling(chand->interested_parties);
-  grpc_pollset_set_destroy(chand->interested_parties);
-  GRPC_COMBINER_UNREF(chand->combiner, "client_channel");
-  GRPC_ERROR_UNREF(chand->disconnect_error);
-  grpc_connectivity_state_destroy(&chand->state_tracker);
-  gpr_mu_destroy(&chand->info_mu);
-  gpr_mu_destroy(&chand->external_connectivity_watcher_list_mu);
+  GRPC_CHANNEL_STACK_UNREF(chand->owning_stack_, "TryToConnect");
 }
 
-/*************************************************************************
- * PER-CALL FUNCTIONS
- */
+grpc_connectivity_state ChannelData::CheckConnectivityState(
+    bool try_to_connect) {
+  grpc_connectivity_state out = grpc_connectivity_state_check(&state_tracker_);
+  if (out == GRPC_CHANNEL_IDLE && try_to_connect) {
+    GRPC_CHANNEL_STACK_REF(owning_stack_, "TryToConnect");
+    GRPC_CLOSURE_SCHED(GRPC_CLOSURE_CREATE(TryToConnectLocked, this,
+                                           grpc_combiner_scheduler(combiner_)),
+                       GRPC_ERROR_NONE);
+  }
+  return out;
+}
 
-// 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
-//   send_initial_metadata
-//   recv_message
-//   send_message
-//   recv_trailing_metadata
-//   send_trailing_metadata
-#define MAX_PENDING_BATCHES 6
+//
+// CallData implementation
+//
 
 // Retry support:
 //
@@ -567,362 +1495,244 @@
 //   (census filter is on top of this one)
 // - add census stats for retries
 
-namespace grpc_core {
-namespace {
-class QueuedPickCanceller;
-}  // namespace
-}  // namespace grpc_core
+CallData::CallData(grpc_call_element* elem, const ChannelData& chand,
+                   const grpc_call_element_args& args)
+    : deadline_state_(elem, args.call_stack, args.call_combiner,
+                      GPR_LIKELY(chand.deadline_checking_enabled())
+                          ? args.deadline
+                          : GRPC_MILLIS_INF_FUTURE),
+      path_(grpc_slice_ref_internal(args.path)),
+      call_start_time_(args.start_time),
+      deadline_(args.deadline),
+      arena_(args.arena),
+      owning_call_(args.call_stack),
+      call_combiner_(args.call_combiner),
+      call_context_(args.context),
+      pending_send_initial_metadata_(false),
+      pending_send_message_(false),
+      pending_send_trailing_metadata_(false),
+      enable_retries_(chand.enable_retries()),
+      retry_committed_(false),
+      last_attempt_got_server_pushback_(false) {}
 
-namespace {
-
-struct call_data;
-
-// State used for starting a retryable batch on a subchannel call.
-// This provides its own grpc_transport_stream_op_batch and other data
-// structures needed to populate the ops in the batch.
-// We allocate one struct on the arena for each attempt at starting a
-// batch on a given subchannel call.
-struct subchannel_batch_data {
-  subchannel_batch_data(grpc_call_element* elem, call_data* calld, int refcount,
-                        bool set_on_complete);
-  // All dtor code must be added in `destroy`. This is because we may
-  // call closures in `subchannel_batch_data` after they are unrefed by
-  // `batch_data_unref`, and msan would complain about accessing this class
-  // after calling dtor. As a result we cannot call the `dtor` in
-  // `batch_data_unref`.
-  // TODO(soheil): We should try to call the dtor in `batch_data_unref`.
-  ~subchannel_batch_data() { destroy(); }
-  void destroy();
-
-  gpr_refcount refs;
-  grpc_call_element* elem;
-  grpc_core::RefCountedPtr<grpc_core::SubchannelCall> subchannel_call;
-  // The batch to use in the subchannel call.
-  // Its payload field points to subchannel_call_retry_state.batch_payload.
-  grpc_transport_stream_op_batch batch;
-  // For intercepting on_complete.
-  grpc_closure on_complete;
-};
-
-// Retry state associated with a subchannel call.
-// Stored in the parent_data of the subchannel call object.
-struct subchannel_call_retry_state {
-  explicit subchannel_call_retry_state(grpc_call_context_element* context)
-      : batch_payload(context),
-        started_send_initial_metadata(false),
-        completed_send_initial_metadata(false),
-        started_send_trailing_metadata(false),
-        completed_send_trailing_metadata(false),
-        started_recv_initial_metadata(false),
-        completed_recv_initial_metadata(false),
-        started_recv_trailing_metadata(false),
-        completed_recv_trailing_metadata(false),
-        retry_dispatched(false) {}
-
-  // subchannel_batch_data.batch.payload points to this.
-  grpc_transport_stream_op_batch_payload batch_payload;
-  // For send_initial_metadata.
-  // Note that we need to make a copy of the initial metadata for each
-  // subchannel call instead of just referring to the copy in call_data,
-  // because filters in the subchannel stack will probably add entries,
-  // so we need to start in a pristine state for each attempt of the call.
-  grpc_linked_mdelem* send_initial_metadata_storage;
-  grpc_metadata_batch send_initial_metadata;
-  // For send_message.
-  grpc_core::ManualConstructor<grpc_core::ByteStreamCache::CachingByteStream>
-      send_message;
-  // For send_trailing_metadata.
-  grpc_linked_mdelem* send_trailing_metadata_storage;
-  grpc_metadata_batch send_trailing_metadata;
-  // For intercepting recv_initial_metadata.
-  grpc_metadata_batch recv_initial_metadata;
-  grpc_closure recv_initial_metadata_ready;
-  bool trailing_metadata_available = false;
-  // For intercepting recv_message.
-  grpc_closure recv_message_ready;
-  grpc_core::OrphanablePtr<grpc_core::ByteStream> recv_message;
-  // For intercepting recv_trailing_metadata.
-  grpc_metadata_batch recv_trailing_metadata;
-  grpc_transport_stream_stats collect_stats;
-  grpc_closure recv_trailing_metadata_ready;
-  // These fields indicate which ops have been started and completed on
-  // this subchannel call.
-  size_t started_send_message_count = 0;
-  size_t completed_send_message_count = 0;
-  size_t started_recv_message_count = 0;
-  size_t completed_recv_message_count = 0;
-  bool started_send_initial_metadata : 1;
-  bool completed_send_initial_metadata : 1;
-  bool started_send_trailing_metadata : 1;
-  bool completed_send_trailing_metadata : 1;
-  bool started_recv_initial_metadata : 1;
-  bool completed_recv_initial_metadata : 1;
-  bool started_recv_trailing_metadata : 1;
-  bool completed_recv_trailing_metadata : 1;
-  // State for callback processing.
-  subchannel_batch_data* recv_initial_metadata_ready_deferred_batch = nullptr;
-  grpc_error* recv_initial_metadata_error = GRPC_ERROR_NONE;
-  subchannel_batch_data* recv_message_ready_deferred_batch = nullptr;
-  grpc_error* recv_message_error = GRPC_ERROR_NONE;
-  subchannel_batch_data* recv_trailing_metadata_internal_batch = nullptr;
-  // NOTE: Do not move this next to the metadata bitfields above. That would
-  //       save space but will also result in a data race because compiler will
-  //       generate a 2 byte store which overwrites the meta-data fields upon
-  //       setting this field.
-  bool retry_dispatched : 1;
-};
-
-// Pending batches stored in call data.
-struct pending_batch {
-  // The pending batch.  If nullptr, this slot is empty.
-  grpc_transport_stream_op_batch* batch;
-  // Indicates whether payload for send ops has been cached in call data.
-  bool send_ops_cached;
-};
-
-/** Call data.  Holds a pointer to SubchannelCall and the
-    associated machinery to create such a pointer.
-    Handles queueing of stream ops until a call object is ready, waiting
-    for initial metadata before trying to create a call object,
-    and handling cancellation gracefully. */
-struct call_data {
-  call_data(grpc_call_element* elem, const channel_data& chand,
-            const grpc_call_element_args& args)
-      : deadline_state(elem, args.call_stack, args.call_combiner,
-                       GPR_LIKELY(chand.deadline_checking_enabled)
-                           ? args.deadline
-                           : GRPC_MILLIS_INF_FUTURE),
-        path(grpc_slice_ref_internal(args.path)),
-        call_start_time(args.start_time),
-        deadline(args.deadline),
-        arena(args.arena),
-        owning_call(args.call_stack),
-        call_combiner(args.call_combiner),
-        call_context(args.context),
-        pending_send_initial_metadata(false),
-        pending_send_message(false),
-        pending_send_trailing_metadata(false),
-        enable_retries(chand.enable_retries),
-        retry_committed(false),
-        last_attempt_got_server_pushback(false) {}
-
-  ~call_data() {
-    grpc_slice_unref_internal(path);
-    GRPC_ERROR_UNREF(cancel_error);
-    for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches); ++i) {
-      GPR_ASSERT(pending_batches[i].batch == nullptr);
-    }
+CallData::~CallData() {
+  grpc_slice_unref_internal(path_);
+  GRPC_ERROR_UNREF(cancel_error_);
+  // Make sure there are no remaining pending batches.
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+    GPR_ASSERT(pending_batches_[i].batch == nullptr);
   }
+}
 
-  // State for handling deadlines.
-  // The code in deadline_filter.c requires this to be the first field.
-  // TODO(roth): This is slightly sub-optimal in that grpc_deadline_state
-  // and this struct both independently store pointers to the call stack
-  // and call combiner.  If/when we have time, find a way to avoid this
-  // without breaking the grpc_deadline_state abstraction.
-  grpc_deadline_state deadline_state;
+grpc_error* CallData::Init(grpc_call_element* elem,
+                           const grpc_call_element_args* args) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  new (elem->call_data) CallData(elem, *chand, *args);
+  return GRPC_ERROR_NONE;
+}
 
-  grpc_slice path;  // Request path.
-  gpr_timespec call_start_time;
-  grpc_millis deadline;
-  gpr_arena* arena;
-  grpc_call_stack* owning_call;
-  grpc_call_combiner* call_combiner;
-  grpc_call_context_element* call_context;
+void CallData::Destroy(grpc_call_element* elem,
+                       const grpc_call_final_info* final_info,
+                       grpc_closure* then_schedule_closure) {
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  if (GPR_LIKELY(calld->subchannel_call_ != nullptr)) {
+    calld->subchannel_call_->SetAfterCallStackDestroy(then_schedule_closure);
+    then_schedule_closure = nullptr;
+  }
+  calld->~CallData();
+  GRPC_CLOSURE_SCHED(then_schedule_closure, GRPC_ERROR_NONE);
+}
 
-  grpc_core::RefCountedPtr<ServerRetryThrottleData> retry_throttle_data;
-  grpc_core::RefCountedPtr<ClientChannelMethodParams> method_params;
+void CallData::StartTransportStreamOpBatch(
+    grpc_call_element* elem, grpc_transport_stream_op_batch* batch) {
+  GPR_TIMER_SCOPE("cc_start_transport_stream_op_batch", 0);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (GPR_LIKELY(chand->deadline_checking_enabled())) {
+    grpc_deadline_state_client_start_transport_stream_op_batch(elem, batch);
+  }
+  // If we've previously been cancelled, immediately fail any new batches.
+  if (GPR_UNLIKELY(calld->cancel_error_ != GRPC_ERROR_NONE)) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+      gpr_log(GPR_INFO, "chand=%p calld=%p: failing batch with error: %s",
+              chand, calld, grpc_error_string(calld->cancel_error_));
+    }
+    // Note: This will release the call combiner.
+    grpc_transport_stream_op_batch_finish_with_failure(
+        batch, GRPC_ERROR_REF(calld->cancel_error_), calld->call_combiner_);
+    return;
+  }
+  // Handle cancellation.
+  if (GPR_UNLIKELY(batch->cancel_stream)) {
+    // Stash a copy of cancel_error in our call data, so that we can use
+    // it for subsequent operations.  This ensures that if the call is
+    // cancelled before any batches are passed down (e.g., if the deadline
+    // is in the past when the call starts), we can return the right
+    // error to the caller when the first batch does get passed down.
+    GRPC_ERROR_UNREF(calld->cancel_error_);
+    calld->cancel_error_ =
+        GRPC_ERROR_REF(batch->payload->cancel_stream.cancel_error);
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+      gpr_log(GPR_INFO, "chand=%p calld=%p: recording cancel_error=%s", chand,
+              calld, grpc_error_string(calld->cancel_error_));
+    }
+    // If we do not have a subchannel call (i.e., a pick has not yet
+    // been started), fail all pending batches.  Otherwise, send the
+    // cancellation down to the subchannel call.
+    if (calld->subchannel_call_ == nullptr) {
+      // TODO(roth): If there is a pending retry callback, do we need to
+      // cancel it here?
+      calld->PendingBatchesFail(elem, GRPC_ERROR_REF(calld->cancel_error_),
+                                NoYieldCallCombiner);
+      // Note: This will release the call combiner.
+      grpc_transport_stream_op_batch_finish_with_failure(
+          batch, GRPC_ERROR_REF(calld->cancel_error_), calld->call_combiner_);
+    } else {
+      // Note: This will release the call combiner.
+      calld->subchannel_call_->StartTransportStreamOpBatch(batch);
+    }
+    return;
+  }
+  // Add the batch to the pending list.
+  calld->PendingBatchesAdd(elem, batch);
+  // Check if we've already gotten a subchannel call.
+  // Note that once we have completed the pick, we do not need to enter
+  // the channel combiner, which is more efficient (especially for
+  // streaming calls).
+  if (calld->subchannel_call_ != nullptr) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+      gpr_log(GPR_INFO,
+              "chand=%p calld=%p: starting batch on subchannel_call=%p", chand,
+              calld, calld->subchannel_call_.get());
+    }
+    calld->PendingBatchesResume(elem);
+    return;
+  }
+  // We do not yet have a subchannel call.
+  // For batches containing a send_initial_metadata op, enter the channel
+  // combiner to start a pick.
+  if (GPR_LIKELY(batch->send_initial_metadata)) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+      gpr_log(GPR_INFO, "chand=%p calld=%p: entering client_channel combiner",
+              chand, calld);
+    }
+    GRPC_CLOSURE_SCHED(
+        GRPC_CLOSURE_INIT(
+            &batch->handler_private.closure, StartPickLocked, elem,
+            grpc_combiner_scheduler(chand->data_plane_combiner())),
+        GRPC_ERROR_NONE);
+  } else {
+    // For all other batches, release the call combiner.
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+      gpr_log(GPR_INFO,
+              "chand=%p calld=%p: saved batch, yielding call combiner", chand,
+              calld);
+    }
+    GRPC_CALL_COMBINER_STOP(calld->call_combiner_,
+                            "batch does not include send_initial_metadata");
+  }
+}
 
-  grpc_core::RefCountedPtr<grpc_core::SubchannelCall> subchannel_call;
-
-  // Set when we get a cancel_stream op.
-  grpc_error* cancel_error = GRPC_ERROR_NONE;
-
-  QueuedPick pick;
-  bool pick_queued = false;
-  bool service_config_applied = false;
-  grpc_core::QueuedPickCanceller* pick_canceller = nullptr;
-  grpc_closure pick_closure;
-
-  grpc_polling_entity* pollent = nullptr;
-
-  // Batches are added to this list when received from above.
-  // They are removed when we are done handling the batch (i.e., when
-  // either we have invoked all of the batch's callbacks or we have
-  // passed the batch down to the subchannel call and are not
-  // intercepting any of its callbacks).
-  pending_batch pending_batches[MAX_PENDING_BATCHES] = {};
-  bool pending_send_initial_metadata : 1;
-  bool pending_send_message : 1;
-  bool pending_send_trailing_metadata : 1;
-
-  // Retry state.
-  bool enable_retries : 1;
-  bool retry_committed : 1;
-  bool last_attempt_got_server_pushback : 1;
-  int num_attempts_completed = 0;
-  size_t bytes_buffered_for_retry = 0;
-  grpc_core::ManualConstructor<grpc_core::BackOff> retry_backoff;
-  grpc_timer retry_timer;
-
-  // The number of pending retriable subchannel batches containing send ops.
-  // We hold a ref to the call stack while this is non-zero, since replay
-  // batches may not complete until after all callbacks have been returned
-  // to the surface, and we need to make sure that the call is not destroyed
-  // until all of these batches have completed.
-  // Note that we actually only need to track replay batches, but it's
-  // easier to track all batches with send ops.
-  int num_pending_retriable_subchannel_send_batches = 0;
-
-  // Cached data for retrying send ops.
-  // send_initial_metadata
-  bool seen_send_initial_metadata = false;
-  grpc_linked_mdelem* send_initial_metadata_storage = nullptr;
-  grpc_metadata_batch send_initial_metadata;
-  uint32_t send_initial_metadata_flags;
-  gpr_atm* peer_string;
-  // send_message
-  // When we get a send_message op, we replace the original byte stream
-  // with a CachingByteStream that caches the slices to a local buffer for
-  // use in retries.
-  // Note: We inline the cache for the first 3 send_message ops and use
-  // dynamic allocation after that.  This number was essentially picked
-  // at random; it could be changed in the future to tune performance.
-  grpc_core::InlinedVector<grpc_core::ByteStreamCache*, 3> send_messages;
-  // send_trailing_metadata
-  bool seen_send_trailing_metadata = false;
-  grpc_linked_mdelem* send_trailing_metadata_storage = nullptr;
-  grpc_metadata_batch send_trailing_metadata;
-};
-
-}  // namespace
-
-// Forward declarations.
-static void retry_commit(grpc_call_element* elem,
-                         subchannel_call_retry_state* retry_state);
-static void start_internal_recv_trailing_metadata(grpc_call_element* elem);
-static void on_complete(void* arg, grpc_error* error);
-static void start_retriable_subchannel_batches(void* arg, grpc_error* ignored);
-static void remove_call_from_queued_picks_locked(grpc_call_element* elem);
+void CallData::SetPollent(grpc_call_element* elem,
+                          grpc_polling_entity* pollent) {
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  calld->pollent_ = pollent;
+}
 
 //
 // send op data caching
 //
 
-// Caches data for send ops so that it can be retried later, if not
-// already cached.
-static void maybe_cache_send_ops_for_batch(call_data* calld,
-                                           pending_batch* pending) {
+void CallData::MaybeCacheSendOpsForBatch(PendingBatch* pending) {
   if (pending->send_ops_cached) return;
   pending->send_ops_cached = true;
   grpc_transport_stream_op_batch* batch = pending->batch;
   // Save a copy of metadata for send_initial_metadata ops.
   if (batch->send_initial_metadata) {
-    calld->seen_send_initial_metadata = true;
-    GPR_ASSERT(calld->send_initial_metadata_storage == nullptr);
+    seen_send_initial_metadata_ = true;
+    GPR_ASSERT(send_initial_metadata_storage_ == nullptr);
     grpc_metadata_batch* send_initial_metadata =
         batch->payload->send_initial_metadata.send_initial_metadata;
-    calld->send_initial_metadata_storage = (grpc_linked_mdelem*)gpr_arena_alloc(
-        calld->arena,
+    send_initial_metadata_storage_ = (grpc_linked_mdelem*)arena_->Alloc(
         sizeof(grpc_linked_mdelem) * send_initial_metadata->list.count);
-    grpc_metadata_batch_copy(send_initial_metadata,
-                             &calld->send_initial_metadata,
-                             calld->send_initial_metadata_storage);
-    calld->send_initial_metadata_flags =
+    grpc_metadata_batch_copy(send_initial_metadata, &send_initial_metadata_,
+                             send_initial_metadata_storage_);
+    send_initial_metadata_flags_ =
         batch->payload->send_initial_metadata.send_initial_metadata_flags;
-    calld->peer_string = batch->payload->send_initial_metadata.peer_string;
+    peer_string_ = batch->payload->send_initial_metadata.peer_string;
   }
   // Set up cache for send_message ops.
   if (batch->send_message) {
-    grpc_core::ByteStreamCache* cache =
-        static_cast<grpc_core::ByteStreamCache*>(
-            gpr_arena_alloc(calld->arena, sizeof(grpc_core::ByteStreamCache)));
-    new (cache) grpc_core::ByteStreamCache(
+    ByteStreamCache* cache = arena_->New<ByteStreamCache>(
         std::move(batch->payload->send_message.send_message));
-    calld->send_messages.push_back(cache);
+    send_messages_.push_back(cache);
   }
   // Save metadata batch for send_trailing_metadata ops.
   if (batch->send_trailing_metadata) {
-    calld->seen_send_trailing_metadata = true;
-    GPR_ASSERT(calld->send_trailing_metadata_storage == nullptr);
+    seen_send_trailing_metadata_ = true;
+    GPR_ASSERT(send_trailing_metadata_storage_ == nullptr);
     grpc_metadata_batch* send_trailing_metadata =
         batch->payload->send_trailing_metadata.send_trailing_metadata;
-    calld->send_trailing_metadata_storage =
-        (grpc_linked_mdelem*)gpr_arena_alloc(
-            calld->arena,
-            sizeof(grpc_linked_mdelem) * send_trailing_metadata->list.count);
-    grpc_metadata_batch_copy(send_trailing_metadata,
-                             &calld->send_trailing_metadata,
-                             calld->send_trailing_metadata_storage);
+    send_trailing_metadata_storage_ = (grpc_linked_mdelem*)arena_->Alloc(
+        sizeof(grpc_linked_mdelem) * send_trailing_metadata->list.count);
+    grpc_metadata_batch_copy(send_trailing_metadata, &send_trailing_metadata_,
+                             send_trailing_metadata_storage_);
   }
 }
 
-// Frees cached send_initial_metadata.
-static void free_cached_send_initial_metadata(channel_data* chand,
-                                              call_data* calld) {
-  if (grpc_client_channel_call_trace.enabled()) {
+void CallData::FreeCachedSendInitialMetadata(ChannelData* chand) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: destroying calld->send_initial_metadata", chand,
-            calld);
+            this);
   }
-  grpc_metadata_batch_destroy(&calld->send_initial_metadata);
+  grpc_metadata_batch_destroy(&send_initial_metadata_);
 }
 
-// Frees cached send_message at index idx.
-static void free_cached_send_message(channel_data* chand, call_data* calld,
-                                     size_t idx) {
-  if (grpc_client_channel_call_trace.enabled()) {
+void CallData::FreeCachedSendMessage(ChannelData* chand, size_t idx) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: destroying calld->send_messages[%" PRIuPTR "]",
-            chand, calld, idx);
+            chand, this, idx);
   }
-  calld->send_messages[idx]->Destroy();
+  send_messages_[idx]->Destroy();
 }
 
-// Frees cached send_trailing_metadata.
-static void free_cached_send_trailing_metadata(channel_data* chand,
-                                               call_data* calld) {
-  if (grpc_client_channel_call_trace.enabled()) {
+void CallData::FreeCachedSendTrailingMetadata(ChannelData* chand) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: destroying calld->send_trailing_metadata",
-            chand, calld);
+            chand, this);
   }
-  grpc_metadata_batch_destroy(&calld->send_trailing_metadata);
+  grpc_metadata_batch_destroy(&send_trailing_metadata_);
 }
 
-// Frees cached send ops that have already been completed after
-// committing the call.
-static void free_cached_send_op_data_after_commit(
-    grpc_call_element* elem, subchannel_call_retry_state* retry_state) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+void CallData::FreeCachedSendOpDataAfterCommit(
+    grpc_call_element* elem, SubchannelCallRetryState* retry_state) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   if (retry_state->completed_send_initial_metadata) {
-    free_cached_send_initial_metadata(chand, calld);
+    FreeCachedSendInitialMetadata(chand);
   }
   for (size_t i = 0; i < retry_state->completed_send_message_count; ++i) {
-    free_cached_send_message(chand, calld, i);
+    FreeCachedSendMessage(chand, i);
   }
   if (retry_state->completed_send_trailing_metadata) {
-    free_cached_send_trailing_metadata(chand, calld);
+    FreeCachedSendTrailingMetadata(chand);
   }
 }
 
-// Frees cached send ops that were completed by the completed batch in
-// batch_data.  Used when batches are completed after the call is committed.
-static void free_cached_send_op_data_for_completed_batch(
-    grpc_call_element* elem, subchannel_batch_data* batch_data,
-    subchannel_call_retry_state* retry_state) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+void CallData::FreeCachedSendOpDataForCompletedBatch(
+    grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+    SubchannelCallRetryState* retry_state) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   if (batch_data->batch.send_initial_metadata) {
-    free_cached_send_initial_metadata(chand, calld);
+    FreeCachedSendInitialMetadata(chand);
   }
   if (batch_data->batch.send_message) {
-    free_cached_send_message(chand, calld,
-                             retry_state->completed_send_message_count - 1);
+    FreeCachedSendMessage(chand, retry_state->completed_send_message_count - 1);
   }
   if (batch_data->batch.send_trailing_metadata) {
-    free_cached_send_trailing_metadata(chand, calld);
+    FreeCachedSendTrailingMetadata(chand);
   }
 }
 
@@ -930,7 +1740,7 @@
 // LB recv_trailing_metadata_ready handling
 //
 
-void maybe_inject_recv_trailing_metadata_ready_for_lb(
+void CallData::MaybeInjectRecvTrailingMetadataReadyForLoadBalancingPolicy(
     const LoadBalancingPolicy::PickArgs& pick,
     grpc_transport_stream_op_batch* batch) {
   if (pick.recv_trailing_metadata_ready != nullptr) {
@@ -949,8 +1759,7 @@
 // pending_batches management
 //
 
-// Returns the index into calld->pending_batches to be used for batch.
-static size_t get_batch_index(grpc_transport_stream_op_batch* batch) {
+size_t CallData::GetBatchIndex(grpc_transport_stream_op_batch* batch) {
   // Note: It is important the send_initial_metadata be the first entry
   // here, since the code in pick_subchannel_locked() assumes it will be.
   if (batch->send_initial_metadata) return 0;
@@ -963,204 +1772,81 @@
 }
 
 // This is called via the call combiner, so access to calld is synchronized.
-static void pending_batches_add(grpc_call_element* elem,
-                                grpc_transport_stream_op_batch* batch) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  const size_t idx = get_batch_index(batch);
-  if (grpc_client_channel_call_trace.enabled()) {
+void CallData::PendingBatchesAdd(grpc_call_element* elem,
+                                 grpc_transport_stream_op_batch* batch) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  const size_t idx = GetBatchIndex(batch);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: adding pending batch at index %" PRIuPTR, chand,
-            calld, idx);
+            this, idx);
   }
-  pending_batch* pending = &calld->pending_batches[idx];
+  PendingBatch* pending = &pending_batches_[idx];
   GPR_ASSERT(pending->batch == nullptr);
   pending->batch = batch;
   pending->send_ops_cached = false;
-  if (calld->enable_retries) {
+  if (enable_retries_) {
     // Update state in calld about pending batches.
     // Also check if the batch takes us over the retry buffer limit.
     // Note: We don't check the size of trailing metadata here, because
     // gRPC clients do not send trailing metadata.
     if (batch->send_initial_metadata) {
-      calld->pending_send_initial_metadata = true;
-      calld->bytes_buffered_for_retry += grpc_metadata_batch_size(
+      pending_send_initial_metadata_ = true;
+      bytes_buffered_for_retry_ += grpc_metadata_batch_size(
           batch->payload->send_initial_metadata.send_initial_metadata);
     }
     if (batch->send_message) {
-      calld->pending_send_message = true;
-      calld->bytes_buffered_for_retry +=
+      pending_send_message_ = true;
+      bytes_buffered_for_retry_ +=
           batch->payload->send_message.send_message->length();
     }
     if (batch->send_trailing_metadata) {
-      calld->pending_send_trailing_metadata = true;
+      pending_send_trailing_metadata_ = true;
     }
-    if (GPR_UNLIKELY(calld->bytes_buffered_for_retry >
-                     chand->per_rpc_retry_buffer_size)) {
-      if (grpc_client_channel_call_trace.enabled()) {
+    if (GPR_UNLIKELY(bytes_buffered_for_retry_ >
+                     chand->per_rpc_retry_buffer_size())) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
         gpr_log(GPR_INFO,
                 "chand=%p calld=%p: exceeded retry buffer size, committing",
-                chand, calld);
+                chand, this);
       }
-      subchannel_call_retry_state* retry_state =
-          calld->subchannel_call == nullptr
-              ? nullptr
-              : static_cast<subchannel_call_retry_state*>(
-
-                    calld->subchannel_call->GetParentData());
-      retry_commit(elem, retry_state);
+      SubchannelCallRetryState* retry_state =
+          subchannel_call_ == nullptr ? nullptr
+                                      : static_cast<SubchannelCallRetryState*>(
+                                            subchannel_call_->GetParentData());
+      RetryCommit(elem, retry_state);
       // If we are not going to retry and have not yet started, pretend
       // retries are disabled so that we don't bother with retry overhead.
-      if (calld->num_attempts_completed == 0) {
-        if (grpc_client_channel_call_trace.enabled()) {
+      if (num_attempts_completed_ == 0) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
           gpr_log(GPR_INFO,
                   "chand=%p calld=%p: disabling retries before first attempt",
-                  chand, calld);
+                  chand, this);
         }
-        calld->enable_retries = false;
+        enable_retries_ = false;
       }
     }
   }
 }
 
-static void pending_batch_clear(call_data* calld, pending_batch* pending) {
-  if (calld->enable_retries) {
+void CallData::PendingBatchClear(PendingBatch* pending) {
+  if (enable_retries_) {
     if (pending->batch->send_initial_metadata) {
-      calld->pending_send_initial_metadata = false;
+      pending_send_initial_metadata_ = false;
     }
     if (pending->batch->send_message) {
-      calld->pending_send_message = false;
+      pending_send_message_ = false;
     }
     if (pending->batch->send_trailing_metadata) {
-      calld->pending_send_trailing_metadata = false;
+      pending_send_trailing_metadata_ = false;
     }
   }
   pending->batch = nullptr;
 }
 
-// This is called via the call combiner, so access to calld is synchronized.
-static void fail_pending_batch_in_call_combiner(void* arg, grpc_error* error) {
-  grpc_transport_stream_op_batch* batch =
-      static_cast<grpc_transport_stream_op_batch*>(arg);
-  call_data* calld = static_cast<call_data*>(batch->handler_private.extra_arg);
-  // Note: This will release the call combiner.
-  grpc_transport_stream_op_batch_finish_with_failure(
-      batch, GRPC_ERROR_REF(error), calld->call_combiner);
-}
-
-// This is called via the call combiner, so access to calld is synchronized.
-// If yield_call_combiner_predicate returns true, assumes responsibility for
-// yielding the call combiner.
-typedef bool (*YieldCallCombinerPredicate)(
-    const grpc_core::CallCombinerClosureList& closures);
-static bool yield_call_combiner(
-    const grpc_core::CallCombinerClosureList& closures) {
-  return true;
-}
-static bool no_yield_call_combiner(
-    const grpc_core::CallCombinerClosureList& closures) {
-  return false;
-}
-static bool yield_call_combiner_if_pending_batches_found(
-    const grpc_core::CallCombinerClosureList& closures) {
-  return closures.size() > 0;
-}
-static void pending_batches_fail(
-    grpc_call_element* elem, grpc_error* error,
-    YieldCallCombinerPredicate yield_call_combiner_predicate) {
-  GPR_ASSERT(error != GRPC_ERROR_NONE);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
-    size_t num_batches = 0;
-    for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-      if (calld->pending_batches[i].batch != nullptr) ++num_batches;
-    }
-    gpr_log(GPR_INFO,
-            "chand=%p calld=%p: failing %" PRIuPTR " pending batches: %s",
-            elem->channel_data, calld, num_batches, grpc_error_string(error));
-  }
-  grpc_core::CallCombinerClosureList closures;
-  for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-    pending_batch* pending = &calld->pending_batches[i];
-    grpc_transport_stream_op_batch* batch = pending->batch;
-    if (batch != nullptr) {
-      if (batch->recv_trailing_metadata) {
-        maybe_inject_recv_trailing_metadata_ready_for_lb(calld->pick.pick,
-                                                         batch);
-      }
-      batch->handler_private.extra_arg = calld;
-      GRPC_CLOSURE_INIT(&batch->handler_private.closure,
-                        fail_pending_batch_in_call_combiner, batch,
-                        grpc_schedule_on_exec_ctx);
-      closures.Add(&batch->handler_private.closure, GRPC_ERROR_REF(error),
-                   "pending_batches_fail");
-      pending_batch_clear(calld, pending);
-    }
-  }
-  if (yield_call_combiner_predicate(closures)) {
-    closures.RunClosures(calld->call_combiner);
-  } else {
-    closures.RunClosuresWithoutYielding(calld->call_combiner);
-  }
-  GRPC_ERROR_UNREF(error);
-}
-
-// This is called via the call combiner, so access to calld is synchronized.
-static void resume_pending_batch_in_call_combiner(void* arg,
-                                                  grpc_error* ignored) {
-  grpc_transport_stream_op_batch* batch =
-      static_cast<grpc_transport_stream_op_batch*>(arg);
-  grpc_core::SubchannelCall* subchannel_call =
-      static_cast<grpc_core::SubchannelCall*>(batch->handler_private.extra_arg);
-  // Note: This will release the call combiner.
-  subchannel_call->StartTransportStreamOpBatch(batch);
-}
-
-// This is called via the call combiner, so access to calld is synchronized.
-static void pending_batches_resume(grpc_call_element* elem) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (calld->enable_retries) {
-    start_retriable_subchannel_batches(elem, GRPC_ERROR_NONE);
-    return;
-  }
-  // Retries not enabled; send down batches as-is.
-  if (grpc_client_channel_call_trace.enabled()) {
-    size_t num_batches = 0;
-    for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-      if (calld->pending_batches[i].batch != nullptr) ++num_batches;
-    }
-    gpr_log(GPR_INFO,
-            "chand=%p calld=%p: starting %" PRIuPTR
-            " pending batches on subchannel_call=%p",
-            chand, calld, num_batches, calld->subchannel_call.get());
-  }
-  grpc_core::CallCombinerClosureList closures;
-  for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-    pending_batch* pending = &calld->pending_batches[i];
-    grpc_transport_stream_op_batch* batch = pending->batch;
-    if (batch != nullptr) {
-      if (batch->recv_trailing_metadata) {
-        maybe_inject_recv_trailing_metadata_ready_for_lb(calld->pick.pick,
-                                                         batch);
-      }
-      batch->handler_private.extra_arg = calld->subchannel_call.get();
-      GRPC_CLOSURE_INIT(&batch->handler_private.closure,
-                        resume_pending_batch_in_call_combiner, batch,
-                        grpc_schedule_on_exec_ctx);
-      closures.Add(&batch->handler_private.closure, GRPC_ERROR_NONE,
-                   "pending_batches_resume");
-      pending_batch_clear(calld, pending);
-    }
-  }
-  // Note: This will release the call combiner.
-  closures.RunClosures(calld->call_combiner);
-}
-
-static void maybe_clear_pending_batch(grpc_call_element* elem,
-                                      pending_batch* pending) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+void CallData::MaybeClearPendingBatch(grpc_call_element* elem,
+                                      PendingBatch* pending) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   grpc_transport_stream_op_batch* batch = pending->batch;
   // We clear the pending batch if all of its callbacks have been
   // scheduled and reset to nullptr.
@@ -1173,30 +1859,128 @@
       (!batch->recv_trailing_metadata ||
        batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready ==
            nullptr)) {
-    if (grpc_client_channel_call_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO, "chand=%p calld=%p: clearing pending batch", chand,
-              calld);
+              this);
     }
-    pending_batch_clear(calld, pending);
+    PendingBatchClear(pending);
   }
 }
 
-// Returns a pointer to the first pending batch for which predicate(batch)
-// returns true, or null if not found.
+// This is called via the call combiner, so access to calld is synchronized.
+void CallData::FailPendingBatchInCallCombiner(void* arg, grpc_error* error) {
+  grpc_transport_stream_op_batch* batch =
+      static_cast<grpc_transport_stream_op_batch*>(arg);
+  CallData* calld = static_cast<CallData*>(batch->handler_private.extra_arg);
+  // Note: This will release the call combiner.
+  grpc_transport_stream_op_batch_finish_with_failure(
+      batch, GRPC_ERROR_REF(error), calld->call_combiner_);
+}
+
+// This is called via the call combiner, so access to calld is synchronized.
+void CallData::PendingBatchesFail(
+    grpc_call_element* elem, grpc_error* error,
+    YieldCallCombinerPredicate yield_call_combiner_predicate) {
+  GPR_ASSERT(error != GRPC_ERROR_NONE);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+    size_t num_batches = 0;
+    for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+      if (pending_batches_[i].batch != nullptr) ++num_batches;
+    }
+    gpr_log(GPR_INFO,
+            "chand=%p calld=%p: failing %" PRIuPTR " pending batches: %s",
+            elem->channel_data, this, num_batches, grpc_error_string(error));
+  }
+  CallCombinerClosureList closures;
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+    PendingBatch* pending = &pending_batches_[i];
+    grpc_transport_stream_op_batch* batch = pending->batch;
+    if (batch != nullptr) {
+      if (batch->recv_trailing_metadata) {
+        MaybeInjectRecvTrailingMetadataReadyForLoadBalancingPolicy(pick_.pick,
+                                                                   batch);
+      }
+      batch->handler_private.extra_arg = this;
+      GRPC_CLOSURE_INIT(&batch->handler_private.closure,
+                        FailPendingBatchInCallCombiner, batch,
+                        grpc_schedule_on_exec_ctx);
+      closures.Add(&batch->handler_private.closure, GRPC_ERROR_REF(error),
+                   "PendingBatchesFail");
+      PendingBatchClear(pending);
+    }
+  }
+  if (yield_call_combiner_predicate(closures)) {
+    closures.RunClosures(call_combiner_);
+  } else {
+    closures.RunClosuresWithoutYielding(call_combiner_);
+  }
+  GRPC_ERROR_UNREF(error);
+}
+
+// This is called via the call combiner, so access to calld is synchronized.
+void CallData::ResumePendingBatchInCallCombiner(void* arg,
+                                                grpc_error* ignored) {
+  grpc_transport_stream_op_batch* batch =
+      static_cast<grpc_transport_stream_op_batch*>(arg);
+  SubchannelCall* subchannel_call =
+      static_cast<SubchannelCall*>(batch->handler_private.extra_arg);
+  // Note: This will release the call combiner.
+  subchannel_call->StartTransportStreamOpBatch(batch);
+}
+
+// This is called via the call combiner, so access to calld is synchronized.
+void CallData::PendingBatchesResume(grpc_call_element* elem) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (enable_retries_) {
+    StartRetriableSubchannelBatches(elem, GRPC_ERROR_NONE);
+    return;
+  }
+  // Retries not enabled; send down batches as-is.
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+    size_t num_batches = 0;
+    for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+      if (pending_batches_[i].batch != nullptr) ++num_batches;
+    }
+    gpr_log(GPR_INFO,
+            "chand=%p calld=%p: starting %" PRIuPTR
+            " pending batches on subchannel_call=%p",
+            chand, this, num_batches, subchannel_call_.get());
+  }
+  CallCombinerClosureList closures;
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+    PendingBatch* pending = &pending_batches_[i];
+    grpc_transport_stream_op_batch* batch = pending->batch;
+    if (batch != nullptr) {
+      if (batch->recv_trailing_metadata) {
+        MaybeInjectRecvTrailingMetadataReadyForLoadBalancingPolicy(pick_.pick,
+                                                                   batch);
+      }
+      batch->handler_private.extra_arg = subchannel_call_.get();
+      GRPC_CLOSURE_INIT(&batch->handler_private.closure,
+                        ResumePendingBatchInCallCombiner, batch,
+                        grpc_schedule_on_exec_ctx);
+      closures.Add(&batch->handler_private.closure, GRPC_ERROR_NONE,
+                   "PendingBatchesResume");
+      PendingBatchClear(pending);
+    }
+  }
+  // Note: This will release the call combiner.
+  closures.RunClosures(call_combiner_);
+}
+
 template <typename Predicate>
-static pending_batch* pending_batch_find(grpc_call_element* elem,
-                                         const char* log_message,
-                                         Predicate predicate) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-    pending_batch* pending = &calld->pending_batches[i];
+CallData::PendingBatch* CallData::PendingBatchFind(grpc_call_element* elem,
+                                                   const char* log_message,
+                                                   Predicate predicate) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+    PendingBatch* pending = &pending_batches_[i];
     grpc_transport_stream_op_batch* batch = pending->batch;
     if (batch != nullptr && predicate(batch)) {
-      if (grpc_client_channel_call_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
         gpr_log(GPR_INFO,
                 "chand=%p calld=%p: %s pending batch at index %" PRIuPTR, chand,
-                calld, log_message, i);
+                this, log_message, i);
       }
       return pending;
     }
@@ -1208,108 +1992,99 @@
 // retry code
 //
 
-// Commits the call so that no further retry attempts will be performed.
-static void retry_commit(grpc_call_element* elem,
-                         subchannel_call_retry_state* retry_state) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (calld->retry_committed) return;
-  calld->retry_committed = true;
-  if (grpc_client_channel_call_trace.enabled()) {
-    gpr_log(GPR_INFO, "chand=%p calld=%p: committing retries", chand, calld);
+void CallData::RetryCommit(grpc_call_element* elem,
+                           SubchannelCallRetryState* retry_state) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (retry_committed_) return;
+  retry_committed_ = true;
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+    gpr_log(GPR_INFO, "chand=%p calld=%p: committing retries", chand, this);
   }
   if (retry_state != nullptr) {
-    free_cached_send_op_data_after_commit(elem, retry_state);
+    FreeCachedSendOpDataAfterCommit(elem, retry_state);
   }
 }
 
-// Starts a retry after appropriate back-off.
-static void do_retry(grpc_call_element* elem,
-                     subchannel_call_retry_state* retry_state,
-                     grpc_millis server_pushback_ms) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  GPR_ASSERT(calld->method_params != nullptr);
-  const ClientChannelMethodParams::RetryPolicy* retry_policy =
-      calld->method_params->retry_policy();
+void CallData::DoRetry(grpc_call_element* elem,
+                       SubchannelCallRetryState* retry_state,
+                       grpc_millis server_pushback_ms) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  GPR_ASSERT(method_params_ != nullptr);
+  const auto* retry_policy = method_params_->retry_policy();
   GPR_ASSERT(retry_policy != nullptr);
   // Reset subchannel call and connected subchannel.
-  calld->subchannel_call.reset();
-  calld->pick.pick.connected_subchannel.reset();
+  subchannel_call_.reset();
+  pick_.pick.connected_subchannel.reset();
   // Compute backoff delay.
   grpc_millis next_attempt_time;
   if (server_pushback_ms >= 0) {
-    next_attempt_time = grpc_core::ExecCtx::Get()->Now() + server_pushback_ms;
-    calld->last_attempt_got_server_pushback = true;
+    next_attempt_time = ExecCtx::Get()->Now() + server_pushback_ms;
+    last_attempt_got_server_pushback_ = true;
   } else {
-    if (calld->num_attempts_completed == 1 ||
-        calld->last_attempt_got_server_pushback) {
-      calld->retry_backoff.Init(
-          grpc_core::BackOff::Options()
+    if (num_attempts_completed_ == 1 || last_attempt_got_server_pushback_) {
+      retry_backoff_.Init(
+          BackOff::Options()
               .set_initial_backoff(retry_policy->initial_backoff)
               .set_multiplier(retry_policy->backoff_multiplier)
               .set_jitter(RETRY_BACKOFF_JITTER)
               .set_max_backoff(retry_policy->max_backoff));
-      calld->last_attempt_got_server_pushback = false;
+      last_attempt_got_server_pushback_ = false;
     }
-    next_attempt_time = calld->retry_backoff->NextAttemptTime();
+    next_attempt_time = retry_backoff_->NextAttemptTime();
   }
-  if (grpc_client_channel_call_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: retrying failed call in %" PRId64 " ms", chand,
-            calld, next_attempt_time - grpc_core::ExecCtx::Get()->Now());
+            this, next_attempt_time - ExecCtx::Get()->Now());
   }
   // Schedule retry after computed delay.
-  GRPC_CLOSURE_INIT(&calld->pick_closure, start_pick_locked, elem,
-                    grpc_combiner_scheduler(chand->combiner));
-  grpc_timer_init(&calld->retry_timer, next_attempt_time, &calld->pick_closure);
+  GRPC_CLOSURE_INIT(&pick_closure_, StartPickLocked, elem,
+                    grpc_combiner_scheduler(chand->data_plane_combiner()));
+  grpc_timer_init(&retry_timer_, next_attempt_time, &pick_closure_);
   // Update bookkeeping.
   if (retry_state != nullptr) retry_state->retry_dispatched = true;
 }
 
-// Returns true if the call is being retried.
-static bool maybe_retry(grpc_call_element* elem,
-                        subchannel_batch_data* batch_data,
-                        grpc_status_code status,
-                        grpc_mdelem* server_pushback_md) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+bool CallData::MaybeRetry(grpc_call_element* elem,
+                          SubchannelCallBatchData* batch_data,
+                          grpc_status_code status,
+                          grpc_mdelem* server_pushback_md) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   // Get retry policy.
-  if (calld->method_params == nullptr) return false;
-  const ClientChannelMethodParams::RetryPolicy* retry_policy =
-      calld->method_params->retry_policy();
+  if (method_params_ == nullptr) return false;
+  const auto* retry_policy = method_params_->retry_policy();
   if (retry_policy == nullptr) return false;
   // If we've already dispatched a retry from this call, return true.
   // This catches the case where the batch has multiple callbacks
   // (i.e., it includes either recv_message or recv_initial_metadata).
-  subchannel_call_retry_state* retry_state = nullptr;
+  SubchannelCallRetryState* retry_state = nullptr;
   if (batch_data != nullptr) {
-    retry_state = static_cast<subchannel_call_retry_state*>(
+    retry_state = static_cast<SubchannelCallRetryState*>(
         batch_data->subchannel_call->GetParentData());
     if (retry_state->retry_dispatched) {
-      if (grpc_client_channel_call_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
         gpr_log(GPR_INFO, "chand=%p calld=%p: retry already dispatched", chand,
-                calld);
+                this);
       }
       return true;
     }
   }
   // Check status.
   if (GPR_LIKELY(status == GRPC_STATUS_OK)) {
-    if (calld->retry_throttle_data != nullptr) {
-      calld->retry_throttle_data->RecordSuccess();
+    if (retry_throttle_data_ != nullptr) {
+      retry_throttle_data_->RecordSuccess();
     }
-    if (grpc_client_channel_call_trace.enabled()) {
-      gpr_log(GPR_INFO, "chand=%p calld=%p: call succeeded", chand, calld);
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+      gpr_log(GPR_INFO, "chand=%p calld=%p: call succeeded", chand, this);
     }
     return false;
   }
   // Status is not OK.  Check whether the status is retryable.
   if (!retry_policy->retryable_status_codes.Contains(status)) {
-    if (grpc_client_channel_call_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: status %s not configured as retryable", chand,
-              calld, grpc_status_code_to_string(status));
+              this, grpc_status_code_to_string(status));
     }
     return false;
   }
@@ -1320,36 +2095,36 @@
   // things like failures due to malformed requests (INVALID_ARGUMENT).
   // Conversely, it's important for this to come before the remaining
   // checks, so that we don't fail to record failures due to other factors.
-  if (calld->retry_throttle_data != nullptr &&
-      !calld->retry_throttle_data->RecordFailure()) {
-    if (grpc_client_channel_call_trace.enabled()) {
-      gpr_log(GPR_INFO, "chand=%p calld=%p: retries throttled", chand, calld);
+  if (retry_throttle_data_ != nullptr &&
+      !retry_throttle_data_->RecordFailure()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
+      gpr_log(GPR_INFO, "chand=%p calld=%p: retries throttled", chand, this);
     }
     return false;
   }
   // Check whether the call is committed.
-  if (calld->retry_committed) {
-    if (grpc_client_channel_call_trace.enabled()) {
+  if (retry_committed_) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO, "chand=%p calld=%p: retries already committed", chand,
-              calld);
+              this);
     }
     return false;
   }
   // Check whether we have retries remaining.
-  ++calld->num_attempts_completed;
-  if (calld->num_attempts_completed >= retry_policy->max_attempts) {
-    if (grpc_client_channel_call_trace.enabled()) {
+  ++num_attempts_completed_;
+  if (num_attempts_completed_ >= retry_policy->max_attempts) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO, "chand=%p calld=%p: exceeded %d retry attempts", chand,
-              calld, retry_policy->max_attempts);
+              this, retry_policy->max_attempts);
     }
     return false;
   }
   // If the call was cancelled from the surface, don't retry.
-  if (calld->cancel_error != GRPC_ERROR_NONE) {
-    if (grpc_client_channel_call_trace.enabled()) {
+  if (cancel_error_ != GRPC_ERROR_NONE) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: call cancelled from surface, not retrying",
-              chand, calld);
+              chand, this);
     }
     return false;
   }
@@ -1359,51 +2134,55 @@
     // If the value is "-1" or any other unparseable string, we do not retry.
     uint32_t ms;
     if (!grpc_parse_slice_to_uint32(GRPC_MDVALUE(*server_pushback_md), &ms)) {
-      if (grpc_client_channel_call_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
         gpr_log(GPR_INFO,
                 "chand=%p calld=%p: not retrying due to server push-back",
-                chand, calld);
+                chand, this);
       }
       return false;
     } else {
-      if (grpc_client_channel_call_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
         gpr_log(GPR_INFO, "chand=%p calld=%p: server push-back: retry in %u ms",
-                chand, calld, ms);
+                chand, this, ms);
       }
       server_pushback_ms = (grpc_millis)ms;
     }
   }
-  do_retry(elem, retry_state, server_pushback_ms);
+  DoRetry(elem, retry_state, server_pushback_ms);
   return true;
 }
 
 //
-// subchannel_batch_data
+// CallData::SubchannelCallBatchData
 //
 
-namespace {
+CallData::SubchannelCallBatchData* CallData::SubchannelCallBatchData::Create(
+    grpc_call_element* elem, int refcount, bool set_on_complete) {
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  return calld->arena_->New<SubchannelCallBatchData>(elem, calld, refcount,
+                                                     set_on_complete);
+}
 
-subchannel_batch_data::subchannel_batch_data(grpc_call_element* elem,
-                                             call_data* calld, int refcount,
-                                             bool set_on_complete)
-    : elem(elem), subchannel_call(calld->subchannel_call) {
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
-          calld->subchannel_call->GetParentData());
+CallData::SubchannelCallBatchData::SubchannelCallBatchData(
+    grpc_call_element* elem, CallData* calld, int refcount,
+    bool set_on_complete)
+    : elem(elem), subchannel_call(calld->subchannel_call_) {
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
+          calld->subchannel_call_->GetParentData());
   batch.payload = &retry_state->batch_payload;
   gpr_ref_init(&refs, refcount);
   if (set_on_complete) {
-    GRPC_CLOSURE_INIT(&on_complete, ::on_complete, this,
+    GRPC_CLOSURE_INIT(&on_complete, CallData::OnComplete, this,
                       grpc_schedule_on_exec_ctx);
     batch.on_complete = &on_complete;
   }
-  GRPC_CALL_STACK_REF(calld->owning_call, "batch_data");
+  GRPC_CALL_STACK_REF(calld->owning_call_, "batch_data");
 }
 
-void subchannel_batch_data::destroy() {
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
-          subchannel_call->GetParentData());
+void CallData::SubchannelCallBatchData::Destroy() {
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(subchannel_call->GetParentData());
   if (batch.send_initial_metadata) {
     grpc_metadata_batch_destroy(&retry_state->send_initial_metadata);
   }
@@ -1417,42 +2196,20 @@
     grpc_metadata_batch_destroy(&retry_state->recv_trailing_metadata);
   }
   subchannel_call.reset();
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  GRPC_CALL_STACK_UNREF(calld->owning_call, "batch_data");
-}
-
-}  // namespace
-
-// Creates a subchannel_batch_data object on the call's arena with the
-// specified refcount.  If set_on_complete is true, the batch's
-// on_complete callback will be set to point to on_complete();
-// otherwise, the batch's on_complete callback will be null.
-static subchannel_batch_data* batch_data_create(grpc_call_element* elem,
-                                                int refcount,
-                                                bool set_on_complete) {
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  subchannel_batch_data* batch_data =
-      new (gpr_arena_alloc(calld->arena, sizeof(*batch_data)))
-          subchannel_batch_data(elem, calld, refcount, set_on_complete);
-  return batch_data;
-}
-
-static void batch_data_unref(subchannel_batch_data* batch_data) {
-  if (gpr_unref(&batch_data->refs)) {
-    batch_data->destroy();
-  }
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  GRPC_CALL_STACK_UNREF(calld->owning_call_, "batch_data");
 }
 
 //
 // recv_initial_metadata callback handling
 //
 
-// Invokes recv_initial_metadata_ready for a subchannel batch.
-static void invoke_recv_initial_metadata_callback(void* arg,
-                                                  grpc_error* error) {
-  subchannel_batch_data* batch_data = static_cast<subchannel_batch_data*>(arg);
+void CallData::InvokeRecvInitialMetadataCallback(void* arg, grpc_error* error) {
+  SubchannelCallBatchData* batch_data =
+      static_cast<SubchannelCallBatchData*>(arg);
+  CallData* calld = static_cast<CallData*>(batch_data->elem->call_data);
   // Find pending batch.
-  pending_batch* pending = pending_batch_find(
+  PendingBatch* pending = calld->PendingBatchFind(
       batch_data->elem, "invoking recv_initial_metadata_ready for",
       [](grpc_transport_stream_op_batch* batch) {
         return batch->recv_initial_metadata &&
@@ -1461,8 +2218,8 @@
       });
   GPR_ASSERT(pending != nullptr);
   // Return metadata.
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   grpc_metadata_batch_move(
       &retry_state->recv_initial_metadata,
@@ -1475,33 +2232,32 @@
           .recv_initial_metadata_ready;
   pending->batch->payload->recv_initial_metadata.recv_initial_metadata_ready =
       nullptr;
-  maybe_clear_pending_batch(batch_data->elem, pending);
-  batch_data_unref(batch_data);
+  calld->MaybeClearPendingBatch(batch_data->elem, pending);
+  batch_data->Unref();
   // Invoke callback.
   GRPC_CLOSURE_RUN(recv_initial_metadata_ready, GRPC_ERROR_REF(error));
 }
 
-// Intercepts recv_initial_metadata_ready callback for retries.
-// Commits the call and returns the initial metadata up the stack.
-static void recv_initial_metadata_ready(void* arg, grpc_error* error) {
-  subchannel_batch_data* batch_data = static_cast<subchannel_batch_data*>(arg);
+void CallData::RecvInitialMetadataReady(void* arg, grpc_error* error) {
+  SubchannelCallBatchData* batch_data =
+      static_cast<SubchannelCallBatchData*>(arg);
   grpc_call_element* elem = batch_data->elem;
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: got recv_initial_metadata_ready, error=%s",
             chand, calld, grpc_error_string(error));
   }
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   retry_state->completed_recv_initial_metadata = true;
   // If a retry was already dispatched, then we're not going to use the
   // result of this recv_initial_metadata op, so do nothing.
   if (retry_state->retry_dispatched) {
     GRPC_CALL_COMBINER_STOP(
-        calld->call_combiner,
+        calld->call_combiner_,
         "recv_initial_metadata_ready after retry dispatched");
     return;
   }
@@ -1512,7 +2268,7 @@
   if (GPR_UNLIKELY((retry_state->trailing_metadata_available ||
                     error != GRPC_ERROR_NONE) &&
                    !retry_state->completed_recv_trailing_metadata)) {
-    if (grpc_client_channel_call_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: deferring recv_initial_metadata_ready "
               "(Trailers-Only)",
@@ -1523,30 +2279,31 @@
     if (!retry_state->started_recv_trailing_metadata) {
       // recv_trailing_metadata not yet started by application; start it
       // ourselves to get status.
-      start_internal_recv_trailing_metadata(elem);
+      calld->StartInternalRecvTrailingMetadata(elem);
     } else {
       GRPC_CALL_COMBINER_STOP(
-          calld->call_combiner,
+          calld->call_combiner_,
           "recv_initial_metadata_ready trailers-only or error");
     }
     return;
   }
   // Received valid initial metadata, so commit the call.
-  retry_commit(elem, retry_state);
+  calld->RetryCommit(elem, retry_state);
   // Invoke the callback to return the result to the surface.
   // Manually invoking a callback function; it does not take ownership of error.
-  invoke_recv_initial_metadata_callback(batch_data, error);
+  calld->InvokeRecvInitialMetadataCallback(batch_data, error);
 }
 
 //
 // recv_message callback handling
 //
 
-// Invokes recv_message_ready for a subchannel batch.
-static void invoke_recv_message_callback(void* arg, grpc_error* error) {
-  subchannel_batch_data* batch_data = static_cast<subchannel_batch_data*>(arg);
+void CallData::InvokeRecvMessageCallback(void* arg, grpc_error* error) {
+  SubchannelCallBatchData* batch_data =
+      static_cast<SubchannelCallBatchData*>(arg);
+  CallData* calld = static_cast<CallData*>(batch_data->elem->call_data);
   // Find pending op.
-  pending_batch* pending = pending_batch_find(
+  PendingBatch* pending = calld->PendingBatchFind(
       batch_data->elem, "invoking recv_message_ready for",
       [](grpc_transport_stream_op_batch* batch) {
         return batch->recv_message &&
@@ -1554,8 +2311,8 @@
       });
   GPR_ASSERT(pending != nullptr);
   // Return payload.
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   *pending->batch->payload->recv_message.recv_message =
       std::move(retry_state->recv_message);
@@ -1565,31 +2322,30 @@
   grpc_closure* recv_message_ready =
       pending->batch->payload->recv_message.recv_message_ready;
   pending->batch->payload->recv_message.recv_message_ready = nullptr;
-  maybe_clear_pending_batch(batch_data->elem, pending);
-  batch_data_unref(batch_data);
+  calld->MaybeClearPendingBatch(batch_data->elem, pending);
+  batch_data->Unref();
   // Invoke callback.
   GRPC_CLOSURE_RUN(recv_message_ready, GRPC_ERROR_REF(error));
 }
 
-// Intercepts recv_message_ready callback for retries.
-// Commits the call and returns the message up the stack.
-static void recv_message_ready(void* arg, grpc_error* error) {
-  subchannel_batch_data* batch_data = static_cast<subchannel_batch_data*>(arg);
+void CallData::RecvMessageReady(void* arg, grpc_error* error) {
+  SubchannelCallBatchData* batch_data =
+      static_cast<SubchannelCallBatchData*>(arg);
   grpc_call_element* elem = batch_data->elem;
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO, "chand=%p calld=%p: got recv_message_ready, error=%s",
             chand, calld, grpc_error_string(error));
   }
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   ++retry_state->completed_recv_message_count;
   // If a retry was already dispatched, then we're not going to use the
   // result of this recv_message op, so do nothing.
   if (retry_state->retry_dispatched) {
-    GRPC_CALL_COMBINER_STOP(calld->call_combiner,
+    GRPC_CALL_COMBINER_STOP(calld->call_combiner_,
                             "recv_message_ready after retry dispatched");
     return;
   }
@@ -1600,7 +2356,7 @@
   if (GPR_UNLIKELY(
           (retry_state->recv_message == nullptr || error != GRPC_ERROR_NONE) &&
           !retry_state->completed_recv_trailing_metadata)) {
-    if (grpc_client_channel_call_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: deferring recv_message_ready (nullptr "
               "message and recv_trailing_metadata pending)",
@@ -1611,33 +2367,29 @@
     if (!retry_state->started_recv_trailing_metadata) {
       // recv_trailing_metadata not yet started by application; start it
       // ourselves to get status.
-      start_internal_recv_trailing_metadata(elem);
+      calld->StartInternalRecvTrailingMetadata(elem);
     } else {
-      GRPC_CALL_COMBINER_STOP(calld->call_combiner, "recv_message_ready null");
+      GRPC_CALL_COMBINER_STOP(calld->call_combiner_, "recv_message_ready null");
     }
     return;
   }
   // Received a valid message, so commit the call.
-  retry_commit(elem, retry_state);
+  calld->RetryCommit(elem, retry_state);
   // Invoke the callback to return the result to the surface.
   // Manually invoking a callback function; it does not take ownership of error.
-  invoke_recv_message_callback(batch_data, error);
+  calld->InvokeRecvMessageCallback(batch_data, error);
 }
 
 //
 // recv_trailing_metadata handling
 //
 
-// Sets *status and *server_pushback_md based on md_batch and error.
-// Only sets *server_pushback_md if server_pushback_md != nullptr.
-static void get_call_status(grpc_call_element* elem,
-                            grpc_metadata_batch* md_batch, grpc_error* error,
-                            grpc_status_code* status,
-                            grpc_mdelem** server_pushback_md) {
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+void CallData::GetCallStatus(grpc_call_element* elem,
+                             grpc_metadata_batch* md_batch, grpc_error* error,
+                             grpc_status_code* status,
+                             grpc_mdelem** server_pushback_md) {
   if (error != GRPC_ERROR_NONE) {
-    grpc_error_get_status(error, calld->deadline, status, nullptr, nullptr,
-                          nullptr);
+    grpc_error_get_status(error, deadline_, status, nullptr, nullptr, nullptr);
   } else {
     GPR_ASSERT(md_batch->idx.named.grpc_status != nullptr);
     *status =
@@ -1650,12 +2402,11 @@
   GRPC_ERROR_UNREF(error);
 }
 
-// Adds recv_trailing_metadata_ready closure to closures.
-static void add_closure_for_recv_trailing_metadata_ready(
-    grpc_call_element* elem, subchannel_batch_data* batch_data,
-    grpc_error* error, grpc_core::CallCombinerClosureList* closures) {
+void CallData::AddClosureForRecvTrailingMetadataReady(
+    grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+    grpc_error* error, CallCombinerClosureList* closures) {
   // Find pending batch.
-  pending_batch* pending = pending_batch_find(
+  PendingBatch* pending = PendingBatchFind(
       elem, "invoking recv_trailing_metadata for",
       [](grpc_transport_stream_op_batch* batch) {
         return batch->recv_trailing_metadata &&
@@ -1663,15 +2414,14 @@
                        .recv_trailing_metadata_ready != nullptr;
       });
   // If we generated the recv_trailing_metadata op internally via
-  // start_internal_recv_trailing_metadata(), then there will be no
-  // pending batch.
+  // StartInternalRecvTrailingMetadata(), then there will be no pending batch.
   if (pending == nullptr) {
     GRPC_ERROR_UNREF(error);
     return;
   }
   // Return metadata.
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   grpc_metadata_batch_move(
       &retry_state->recv_trailing_metadata,
@@ -1683,20 +2433,18 @@
   // Update bookkeeping.
   pending->batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready =
       nullptr;
-  maybe_clear_pending_batch(elem, pending);
+  MaybeClearPendingBatch(elem, pending);
 }
 
-// Adds any necessary closures for deferred recv_initial_metadata and
-// recv_message callbacks to closures.
-static void add_closures_for_deferred_recv_callbacks(
-    subchannel_batch_data* batch_data, subchannel_call_retry_state* retry_state,
-    grpc_core::CallCombinerClosureList* closures) {
+void CallData::AddClosuresForDeferredRecvCallbacks(
+    SubchannelCallBatchData* batch_data, SubchannelCallRetryState* retry_state,
+    CallCombinerClosureList* closures) {
   if (batch_data->batch.recv_trailing_metadata) {
     // Add closure for deferred recv_initial_metadata_ready.
     if (GPR_UNLIKELY(retry_state->recv_initial_metadata_ready_deferred_batch !=
                      nullptr)) {
       GRPC_CLOSURE_INIT(&retry_state->recv_initial_metadata_ready,
-                        invoke_recv_initial_metadata_callback,
+                        InvokeRecvInitialMetadataCallback,
                         retry_state->recv_initial_metadata_ready_deferred_batch,
                         grpc_schedule_on_exec_ctx);
       closures->Add(&retry_state->recv_initial_metadata_ready,
@@ -1708,7 +2456,7 @@
     if (GPR_UNLIKELY(retry_state->recv_message_ready_deferred_batch !=
                      nullptr)) {
       GRPC_CLOSURE_INIT(&retry_state->recv_message_ready,
-                        invoke_recv_message_callback,
+                        InvokeRecvMessageCallback,
                         retry_state->recv_message_ready_deferred_batch,
                         grpc_schedule_on_exec_ctx);
       closures->Add(&retry_state->recv_message_ready,
@@ -1719,11 +2467,8 @@
   }
 }
 
-// Returns true if any op in the batch was not yet started.
-// Only looks at send ops, since recv ops are always started immediately.
-static bool pending_batch_is_unstarted(
-    pending_batch* pending, call_data* calld,
-    subchannel_call_retry_state* retry_state) {
+bool CallData::PendingBatchIsUnstarted(PendingBatch* pending,
+                                       SubchannelCallRetryState* retry_state) {
   if (pending->batch == nullptr || pending->batch->on_complete == nullptr) {
     return false;
   }
@@ -1732,7 +2477,7 @@
     return true;
   }
   if (pending->batch->send_message &&
-      retry_state->started_send_message_count < calld->send_messages.size()) {
+      retry_state->started_send_message_count < send_messages_.size()) {
     return true;
   }
   if (pending->batch->send_trailing_metadata &&
@@ -1742,72 +2487,66 @@
   return false;
 }
 
-// For any pending batch containing an op that has not yet been started,
-// adds the pending batch's completion closures to closures.
-static void add_closures_to_fail_unstarted_pending_batches(
-    grpc_call_element* elem, subchannel_call_retry_state* retry_state,
-    grpc_error* error, grpc_core::CallCombinerClosureList* closures) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-    pending_batch* pending = &calld->pending_batches[i];
-    if (pending_batch_is_unstarted(pending, calld, retry_state)) {
-      if (grpc_client_channel_call_trace.enabled()) {
+void CallData::AddClosuresToFailUnstartedPendingBatches(
+    grpc_call_element* elem, SubchannelCallRetryState* retry_state,
+    grpc_error* error, CallCombinerClosureList* closures) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+    PendingBatch* pending = &pending_batches_[i];
+    if (PendingBatchIsUnstarted(pending, retry_state)) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
         gpr_log(GPR_INFO,
                 "chand=%p calld=%p: failing unstarted pending batch at index "
                 "%" PRIuPTR,
-                chand, calld, i);
+                chand, this, i);
       }
       closures->Add(pending->batch->on_complete, GRPC_ERROR_REF(error),
                     "failing on_complete for pending batch");
       pending->batch->on_complete = nullptr;
-      maybe_clear_pending_batch(elem, pending);
+      MaybeClearPendingBatch(elem, pending);
     }
   }
   GRPC_ERROR_UNREF(error);
 }
 
-// Runs necessary closures upon completion of a call attempt.
-static void run_closures_for_completed_call(subchannel_batch_data* batch_data,
-                                            grpc_error* error) {
+void CallData::RunClosuresForCompletedCall(SubchannelCallBatchData* batch_data,
+                                           grpc_error* error) {
   grpc_call_element* elem = batch_data->elem;
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   // Construct list of closures to execute.
-  grpc_core::CallCombinerClosureList closures;
+  CallCombinerClosureList closures;
   // First, add closure for recv_trailing_metadata_ready.
-  add_closure_for_recv_trailing_metadata_ready(
-      elem, batch_data, GRPC_ERROR_REF(error), &closures);
+  AddClosureForRecvTrailingMetadataReady(elem, batch_data,
+                                         GRPC_ERROR_REF(error), &closures);
   // If there are deferred recv_initial_metadata_ready or recv_message_ready
   // callbacks, add them to closures.
-  add_closures_for_deferred_recv_callbacks(batch_data, retry_state, &closures);
+  AddClosuresForDeferredRecvCallbacks(batch_data, retry_state, &closures);
   // Add closures to fail any pending batches that have not yet been started.
-  add_closures_to_fail_unstarted_pending_batches(
-      elem, retry_state, GRPC_ERROR_REF(error), &closures);
+  AddClosuresToFailUnstartedPendingBatches(elem, retry_state,
+                                           GRPC_ERROR_REF(error), &closures);
   // Don't need batch_data anymore.
-  batch_data_unref(batch_data);
+  batch_data->Unref();
   // Schedule all of the closures identified above.
   // Note: This will release the call combiner.
-  closures.RunClosures(calld->call_combiner);
+  closures.RunClosures(call_combiner_);
   GRPC_ERROR_UNREF(error);
 }
 
-// Intercepts recv_trailing_metadata_ready callback for retries.
-// Commits the call and returns the trailing metadata up the stack.
-static void recv_trailing_metadata_ready(void* arg, grpc_error* error) {
-  subchannel_batch_data* batch_data = static_cast<subchannel_batch_data*>(arg);
+void CallData::RecvTrailingMetadataReady(void* arg, grpc_error* error) {
+  SubchannelCallBatchData* batch_data =
+      static_cast<SubchannelCallBatchData*>(arg);
   grpc_call_element* elem = batch_data->elem;
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: got recv_trailing_metadata_ready, error=%s",
             chand, calld, grpc_error_string(error));
   }
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   retry_state->completed_recv_trailing_metadata = true;
   // Get the call's status and check for server pushback metadata.
@@ -1815,44 +2554,42 @@
   grpc_mdelem* server_pushback_md = nullptr;
   grpc_metadata_batch* md_batch =
       batch_data->batch.payload->recv_trailing_metadata.recv_trailing_metadata;
-  get_call_status(elem, md_batch, GRPC_ERROR_REF(error), &status,
-                  &server_pushback_md);
-  if (grpc_client_channel_call_trace.enabled()) {
+  calld->GetCallStatus(elem, md_batch, GRPC_ERROR_REF(error), &status,
+                       &server_pushback_md);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO, "chand=%p calld=%p: call finished, status=%s", chand,
             calld, grpc_status_code_to_string(status));
   }
   // Check if we should retry.
-  if (maybe_retry(elem, batch_data, status, server_pushback_md)) {
+  if (calld->MaybeRetry(elem, batch_data, status, server_pushback_md)) {
     // Unref batch_data for deferred recv_initial_metadata_ready or
     // recv_message_ready callbacks, if any.
     if (retry_state->recv_initial_metadata_ready_deferred_batch != nullptr) {
-      batch_data_unref(batch_data);
+      batch_data->Unref();
       GRPC_ERROR_UNREF(retry_state->recv_initial_metadata_error);
     }
     if (retry_state->recv_message_ready_deferred_batch != nullptr) {
-      batch_data_unref(batch_data);
+      batch_data->Unref();
       GRPC_ERROR_UNREF(retry_state->recv_message_error);
     }
-    batch_data_unref(batch_data);
+    batch_data->Unref();
     return;
   }
   // Not retrying, so commit the call.
-  retry_commit(elem, retry_state);
+  calld->RetryCommit(elem, retry_state);
   // Run any necessary closures.
-  run_closures_for_completed_call(batch_data, GRPC_ERROR_REF(error));
+  calld->RunClosuresForCompletedCall(batch_data, GRPC_ERROR_REF(error));
 }
 
 //
 // on_complete callback handling
 //
 
-// Adds the on_complete closure for the pending batch completed in
-// batch_data to closures.
-static void add_closure_for_completed_pending_batch(
-    grpc_call_element* elem, subchannel_batch_data* batch_data,
-    subchannel_call_retry_state* retry_state, grpc_error* error,
-    grpc_core::CallCombinerClosureList* closures) {
-  pending_batch* pending = pending_batch_find(
+void CallData::AddClosuresForCompletedPendingBatch(
+    grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+    SubchannelCallRetryState* retry_state, grpc_error* error,
+    CallCombinerClosureList* closures) {
+  PendingBatch* pending = PendingBatchFind(
       elem, "completed", [batch_data](grpc_transport_stream_op_batch* batch) {
         // Match the pending batch with the same set of send ops as the
         // subchannel batch we've just completed.
@@ -1873,27 +2610,22 @@
   closures->Add(pending->batch->on_complete, error,
                 "on_complete for pending batch");
   pending->batch->on_complete = nullptr;
-  maybe_clear_pending_batch(elem, pending);
+  MaybeClearPendingBatch(elem, pending);
 }
 
-// If there are any cached ops to replay or pending ops to start on the
-// subchannel call, adds a closure to closures to invoke
-// start_retriable_subchannel_batches().
-static void add_closures_for_replay_or_pending_send_ops(
-    grpc_call_element* elem, subchannel_batch_data* batch_data,
-    subchannel_call_retry_state* retry_state,
-    grpc_core::CallCombinerClosureList* closures) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+void CallData::AddClosuresForReplayOrPendingSendOps(
+    grpc_call_element* elem, SubchannelCallBatchData* batch_data,
+    SubchannelCallRetryState* retry_state, CallCombinerClosureList* closures) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   bool have_pending_send_message_ops =
-      retry_state->started_send_message_count < calld->send_messages.size();
+      retry_state->started_send_message_count < send_messages_.size();
   bool have_pending_send_trailing_metadata_op =
-      calld->seen_send_trailing_metadata &&
+      seen_send_trailing_metadata_ &&
       !retry_state->started_send_trailing_metadata;
   if (!have_pending_send_message_ops &&
       !have_pending_send_trailing_metadata_op) {
-    for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-      pending_batch* pending = &calld->pending_batches[i];
+    for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+      PendingBatch* pending = &pending_batches_[i];
       grpc_transport_stream_op_batch* batch = pending->batch;
       if (batch == nullptr || pending->send_ops_cached) continue;
       if (batch->send_message) have_pending_send_message_ops = true;
@@ -1903,34 +2635,33 @@
     }
   }
   if (have_pending_send_message_ops || have_pending_send_trailing_metadata_op) {
-    if (grpc_client_channel_call_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: starting next batch for pending send op(s)",
-              chand, calld);
+              chand, this);
     }
     GRPC_CLOSURE_INIT(&batch_data->batch.handler_private.closure,
-                      start_retriable_subchannel_batches, elem,
+                      StartRetriableSubchannelBatches, elem,
                       grpc_schedule_on_exec_ctx);
     closures->Add(&batch_data->batch.handler_private.closure, GRPC_ERROR_NONE,
                   "starting next batch for send_* op(s)");
   }
 }
 
-// Callback used to intercept on_complete from subchannel calls.
-// Called only when retries are enabled.
-static void on_complete(void* arg, grpc_error* error) {
-  subchannel_batch_data* batch_data = static_cast<subchannel_batch_data*>(arg);
+void CallData::OnComplete(void* arg, grpc_error* error) {
+  SubchannelCallBatchData* batch_data =
+      static_cast<SubchannelCallBatchData*>(arg);
   grpc_call_element* elem = batch_data->elem;
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     char* batch_str = grpc_transport_stream_op_batch_string(&batch_data->batch);
     gpr_log(GPR_INFO, "chand=%p calld=%p: got on_complete, error=%s, batch=%s",
             chand, calld, grpc_error_string(error), batch_str);
     gpr_free(batch_str);
   }
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
           batch_data->subchannel_call->GetParentData());
   // Update bookkeeping in retry_state.
   if (batch_data->batch.send_initial_metadata) {
@@ -1944,38 +2675,38 @@
   }
   // If the call is committed, free cached data for send ops that we've just
   // completed.
-  if (calld->retry_committed) {
-    free_cached_send_op_data_for_completed_batch(elem, batch_data, retry_state);
+  if (calld->retry_committed_) {
+    calld->FreeCachedSendOpDataForCompletedBatch(elem, batch_data, retry_state);
   }
   // Construct list of closures to execute.
-  grpc_core::CallCombinerClosureList closures;
+  CallCombinerClosureList closures;
   // If a retry was already dispatched, that means we saw
   // recv_trailing_metadata before this, so we do nothing here.
   // Otherwise, invoke the callback to return the result to the surface.
   if (!retry_state->retry_dispatched) {
     // Add closure for the completed pending batch, if any.
-    add_closure_for_completed_pending_batch(elem, batch_data, retry_state,
-                                            GRPC_ERROR_REF(error), &closures);
+    calld->AddClosuresForCompletedPendingBatch(
+        elem, batch_data, retry_state, GRPC_ERROR_REF(error), &closures);
     // If needed, add a callback to start any replay or pending send ops on
     // the subchannel call.
     if (!retry_state->completed_recv_trailing_metadata) {
-      add_closures_for_replay_or_pending_send_ops(elem, batch_data, retry_state,
+      calld->AddClosuresForReplayOrPendingSendOps(elem, batch_data, retry_state,
                                                   &closures);
     }
   }
   // Track number of pending subchannel send batches and determine if this
   // was the last one.
-  --calld->num_pending_retriable_subchannel_send_batches;
+  --calld->num_pending_retriable_subchannel_send_batches_;
   const bool last_send_batch_complete =
-      calld->num_pending_retriable_subchannel_send_batches == 0;
+      calld->num_pending_retriable_subchannel_send_batches_ == 0;
   // Don't need batch_data anymore.
-  batch_data_unref(batch_data);
+  batch_data->Unref();
   // Schedule all of the closures identified above.
   // Note: This yeilds the call combiner.
-  closures.RunClosures(calld->call_combiner);
+  closures.RunClosures(calld->call_combiner_);
   // If this was the last subchannel send batch, unref the call stack.
   if (last_send_batch_complete) {
-    GRPC_CALL_STACK_UNREF(calld->owning_call, "subchannel_send_batches");
+    GRPC_CALL_STACK_UNREF(calld->owning_call_, "subchannel_send_batches");
   }
 }
 
@@ -1983,40 +2714,35 @@
 // subchannel batch construction
 //
 
-// Helper function used to start a subchannel batch in the call combiner.
-static void start_batch_in_call_combiner(void* arg, grpc_error* ignored) {
+void CallData::StartBatchInCallCombiner(void* arg, grpc_error* ignored) {
   grpc_transport_stream_op_batch* batch =
       static_cast<grpc_transport_stream_op_batch*>(arg);
-  grpc_core::SubchannelCall* subchannel_call =
-      static_cast<grpc_core::SubchannelCall*>(batch->handler_private.extra_arg);
+  SubchannelCall* subchannel_call =
+      static_cast<SubchannelCall*>(batch->handler_private.extra_arg);
   // Note: This will release the call combiner.
   subchannel_call->StartTransportStreamOpBatch(batch);
 }
 
-// Adds a closure to closures that will execute batch in the call combiner.
-static void add_closure_for_subchannel_batch(
+void CallData::AddClosureForSubchannelBatch(
     grpc_call_element* elem, grpc_transport_stream_op_batch* batch,
-    grpc_core::CallCombinerClosureList* closures) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  batch->handler_private.extra_arg = calld->subchannel_call.get();
-  GRPC_CLOSURE_INIT(&batch->handler_private.closure,
-                    start_batch_in_call_combiner, batch,
-                    grpc_schedule_on_exec_ctx);
-  if (grpc_client_channel_call_trace.enabled()) {
+    CallCombinerClosureList* closures) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  batch->handler_private.extra_arg = subchannel_call_.get();
+  GRPC_CLOSURE_INIT(&batch->handler_private.closure, StartBatchInCallCombiner,
+                    batch, grpc_schedule_on_exec_ctx);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     char* batch_str = grpc_transport_stream_op_batch_string(batch);
     gpr_log(GPR_INFO, "chand=%p calld=%p: starting subchannel batch: %s", chand,
-            calld, batch_str);
+            this, batch_str);
     gpr_free(batch_str);
   }
   closures->Add(&batch->handler_private.closure, GRPC_ERROR_NONE,
                 "start_subchannel_batch");
 }
 
-// Adds retriable send_initial_metadata op to batch_data.
-static void add_retriable_send_initial_metadata_op(
-    call_data* calld, subchannel_call_retry_state* retry_state,
-    subchannel_batch_data* batch_data) {
+void CallData::AddRetriableSendInitialMetadataOp(
+    SubchannelCallRetryState* retry_state,
+    SubchannelCallBatchData* batch_data) {
   // Maps the number of retries to the corresponding metadata value slice.
   static const grpc_slice* retry_count_strings[] = {
       &GRPC_MDSTR_1, &GRPC_MDSTR_2, &GRPC_MDSTR_3, &GRPC_MDSTR_4};
@@ -2027,11 +2753,10 @@
   // If we've already completed one or more attempts, add the
   // grpc-retry-attempts header.
   retry_state->send_initial_metadata_storage =
-      static_cast<grpc_linked_mdelem*>(gpr_arena_alloc(
-          calld->arena, sizeof(grpc_linked_mdelem) *
-                            (calld->send_initial_metadata.list.count +
-                             (calld->num_attempts_completed > 0))));
-  grpc_metadata_batch_copy(&calld->send_initial_metadata,
+      static_cast<grpc_linked_mdelem*>(arena_->Alloc(
+          sizeof(grpc_linked_mdelem) *
+          (send_initial_metadata_.list.count + (num_attempts_completed_ > 0))));
+  grpc_metadata_batch_copy(&send_initial_metadata_,
                            &retry_state->send_initial_metadata,
                            retry_state->send_initial_metadata_storage);
   if (GPR_UNLIKELY(retry_state->send_initial_metadata.idx.named
@@ -2040,14 +2765,14 @@
                                retry_state->send_initial_metadata.idx.named
                                    .grpc_previous_rpc_attempts);
   }
-  if (GPR_UNLIKELY(calld->num_attempts_completed > 0)) {
+  if (GPR_UNLIKELY(num_attempts_completed_ > 0)) {
     grpc_mdelem retry_md = grpc_mdelem_create(
         GRPC_MDSTR_GRPC_PREVIOUS_RPC_ATTEMPTS,
-        *retry_count_strings[calld->num_attempts_completed - 1], nullptr);
+        *retry_count_strings[num_attempts_completed_ - 1], nullptr);
     grpc_error* error = grpc_metadata_batch_add_tail(
         &retry_state->send_initial_metadata,
-        &retry_state->send_initial_metadata_storage[calld->send_initial_metadata
-                                                        .list.count],
+        &retry_state
+             ->send_initial_metadata_storage[send_initial_metadata_.list.count],
         retry_md);
     if (GPR_UNLIKELY(error != GRPC_ERROR_NONE)) {
       gpr_log(GPR_ERROR, "error adding retry metadata: %s",
@@ -2060,24 +2785,21 @@
   batch_data->batch.payload->send_initial_metadata.send_initial_metadata =
       &retry_state->send_initial_metadata;
   batch_data->batch.payload->send_initial_metadata.send_initial_metadata_flags =
-      calld->send_initial_metadata_flags;
-  batch_data->batch.payload->send_initial_metadata.peer_string =
-      calld->peer_string;
+      send_initial_metadata_flags_;
+  batch_data->batch.payload->send_initial_metadata.peer_string = peer_string_;
 }
 
-// Adds retriable send_message op to batch_data.
-static void add_retriable_send_message_op(
-    grpc_call_element* elem, subchannel_call_retry_state* retry_state,
-    subchannel_batch_data* batch_data) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
+void CallData::AddRetriableSendMessageOp(grpc_call_element* elem,
+                                         SubchannelCallRetryState* retry_state,
+                                         SubchannelCallBatchData* batch_data) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: starting calld->send_messages[%" PRIuPTR "]",
-            chand, calld, retry_state->started_send_message_count);
+            chand, this, retry_state->started_send_message_count);
   }
-  grpc_core::ByteStreamCache* cache =
-      calld->send_messages[retry_state->started_send_message_count];
+  ByteStreamCache* cache =
+      send_messages_[retry_state->started_send_message_count];
   ++retry_state->started_send_message_count;
   retry_state->send_message.Init(cache);
   batch_data->batch.send_message = true;
@@ -2085,18 +2807,16 @@
       retry_state->send_message.get());
 }
 
-// Adds retriable send_trailing_metadata op to batch_data.
-static void add_retriable_send_trailing_metadata_op(
-    call_data* calld, subchannel_call_retry_state* retry_state,
-    subchannel_batch_data* batch_data) {
+void CallData::AddRetriableSendTrailingMetadataOp(
+    SubchannelCallRetryState* retry_state,
+    SubchannelCallBatchData* batch_data) {
   // We need to make a copy of the metadata batch for each attempt, since
   // the filters in the subchannel stack may modify this batch, and we don't
   // want those modifications to be passed forward to subsequent attempts.
   retry_state->send_trailing_metadata_storage =
-      static_cast<grpc_linked_mdelem*>(gpr_arena_alloc(
-          calld->arena, sizeof(grpc_linked_mdelem) *
-                            calld->send_trailing_metadata.list.count));
-  grpc_metadata_batch_copy(&calld->send_trailing_metadata,
+      static_cast<grpc_linked_mdelem*>(arena_->Alloc(
+          sizeof(grpc_linked_mdelem) * send_trailing_metadata_.list.count));
+  grpc_metadata_batch_copy(&send_trailing_metadata_,
                            &retry_state->send_trailing_metadata,
                            retry_state->send_trailing_metadata_storage);
   retry_state->started_send_trailing_metadata = true;
@@ -2105,10 +2825,9 @@
       &retry_state->send_trailing_metadata;
 }
 
-// Adds retriable recv_initial_metadata op to batch_data.
-static void add_retriable_recv_initial_metadata_op(
-    call_data* calld, subchannel_call_retry_state* retry_state,
-    subchannel_batch_data* batch_data) {
+void CallData::AddRetriableRecvInitialMetadataOp(
+    SubchannelCallRetryState* retry_state,
+    SubchannelCallBatchData* batch_data) {
   retry_state->started_recv_initial_metadata = true;
   batch_data->batch.recv_initial_metadata = true;
   grpc_metadata_batch_init(&retry_state->recv_initial_metadata);
@@ -2117,30 +2836,27 @@
   batch_data->batch.payload->recv_initial_metadata.trailing_metadata_available =
       &retry_state->trailing_metadata_available;
   GRPC_CLOSURE_INIT(&retry_state->recv_initial_metadata_ready,
-                    recv_initial_metadata_ready, batch_data,
+                    RecvInitialMetadataReady, batch_data,
                     grpc_schedule_on_exec_ctx);
   batch_data->batch.payload->recv_initial_metadata.recv_initial_metadata_ready =
       &retry_state->recv_initial_metadata_ready;
 }
 
-// Adds retriable recv_message op to batch_data.
-static void add_retriable_recv_message_op(
-    call_data* calld, subchannel_call_retry_state* retry_state,
-    subchannel_batch_data* batch_data) {
+void CallData::AddRetriableRecvMessageOp(SubchannelCallRetryState* retry_state,
+                                         SubchannelCallBatchData* batch_data) {
   ++retry_state->started_recv_message_count;
   batch_data->batch.recv_message = true;
   batch_data->batch.payload->recv_message.recv_message =
       &retry_state->recv_message;
-  GRPC_CLOSURE_INIT(&retry_state->recv_message_ready, recv_message_ready,
+  GRPC_CLOSURE_INIT(&retry_state->recv_message_ready, RecvMessageReady,
                     batch_data, grpc_schedule_on_exec_ctx);
   batch_data->batch.payload->recv_message.recv_message_ready =
       &retry_state->recv_message_ready;
 }
 
-// Adds retriable recv_trailing_metadata op to batch_data.
-static void add_retriable_recv_trailing_metadata_op(
-    call_data* calld, subchannel_call_retry_state* retry_state,
-    subchannel_batch_data* batch_data) {
+void CallData::AddRetriableRecvTrailingMetadataOp(
+    SubchannelCallRetryState* retry_state,
+    SubchannelCallBatchData* batch_data) {
   retry_state->started_recv_trailing_metadata = true;
   batch_data->batch.recv_trailing_metadata = true;
   grpc_metadata_batch_init(&retry_state->recv_trailing_metadata);
@@ -2149,115 +2865,105 @@
   batch_data->batch.payload->recv_trailing_metadata.collect_stats =
       &retry_state->collect_stats;
   GRPC_CLOSURE_INIT(&retry_state->recv_trailing_metadata_ready,
-                    recv_trailing_metadata_ready, batch_data,
+                    RecvTrailingMetadataReady, batch_data,
                     grpc_schedule_on_exec_ctx);
   batch_data->batch.payload->recv_trailing_metadata
       .recv_trailing_metadata_ready =
       &retry_state->recv_trailing_metadata_ready;
-  maybe_inject_recv_trailing_metadata_ready_for_lb(calld->pick.pick,
-                                                   &batch_data->batch);
+  MaybeInjectRecvTrailingMetadataReadyForLoadBalancingPolicy(
+      pick_.pick, &batch_data->batch);
 }
 
-// Helper function used to start a recv_trailing_metadata batch.  This
-// is used in the case where a recv_initial_metadata or recv_message
-// op fails in a way that we know the call is over but when the application
-// has not yet started its own recv_trailing_metadata op.
-static void start_internal_recv_trailing_metadata(grpc_call_element* elem) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
+void CallData::StartInternalRecvTrailingMetadata(grpc_call_element* elem) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: call failed but recv_trailing_metadata not "
             "started; starting it internally",
-            chand, calld);
+            chand, this);
   }
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
-          calld->subchannel_call->GetParentData());
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(subchannel_call_->GetParentData());
   // Create batch_data with 2 refs, since this batch will be unreffed twice:
   // once for the recv_trailing_metadata_ready callback when the subchannel
   // batch returns, and again when we actually get a recv_trailing_metadata
   // op from the surface.
-  subchannel_batch_data* batch_data =
-      batch_data_create(elem, 2, false /* set_on_complete */);
-  add_retriable_recv_trailing_metadata_op(calld, retry_state, batch_data);
+  SubchannelCallBatchData* batch_data =
+      SubchannelCallBatchData::Create(elem, 2, false /* set_on_complete */);
+  AddRetriableRecvTrailingMetadataOp(retry_state, batch_data);
   retry_state->recv_trailing_metadata_internal_batch = batch_data;
   // Note: This will release the call combiner.
-  calld->subchannel_call->StartTransportStreamOpBatch(&batch_data->batch);
+  subchannel_call_->StartTransportStreamOpBatch(&batch_data->batch);
 }
 
 // If there are any cached send ops that need to be replayed on the
 // current subchannel call, creates and returns a new subchannel batch
 // to replay those ops.  Otherwise, returns nullptr.
-static subchannel_batch_data* maybe_create_subchannel_batch_for_replay(
-    grpc_call_element* elem, subchannel_call_retry_state* retry_state) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  subchannel_batch_data* replay_batch_data = nullptr;
+CallData::SubchannelCallBatchData*
+CallData::MaybeCreateSubchannelBatchForReplay(
+    grpc_call_element* elem, SubchannelCallRetryState* retry_state) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  SubchannelCallBatchData* replay_batch_data = nullptr;
   // send_initial_metadata.
-  if (calld->seen_send_initial_metadata &&
+  if (seen_send_initial_metadata_ &&
       !retry_state->started_send_initial_metadata &&
-      !calld->pending_send_initial_metadata) {
-    if (grpc_client_channel_call_trace.enabled()) {
+      !pending_send_initial_metadata_) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: replaying previously completed "
               "send_initial_metadata op",
-              chand, calld);
+              chand, this);
     }
-    replay_batch_data = batch_data_create(elem, 1, true /* set_on_complete */);
-    add_retriable_send_initial_metadata_op(calld, retry_state,
-                                           replay_batch_data);
+    replay_batch_data =
+        SubchannelCallBatchData::Create(elem, 1, true /* set_on_complete */);
+    AddRetriableSendInitialMetadataOp(retry_state, replay_batch_data);
   }
   // send_message.
   // Note that we can only have one send_message op in flight at a time.
-  if (retry_state->started_send_message_count < calld->send_messages.size() &&
+  if (retry_state->started_send_message_count < send_messages_.size() &&
       retry_state->started_send_message_count ==
           retry_state->completed_send_message_count &&
-      !calld->pending_send_message) {
-    if (grpc_client_channel_call_trace.enabled()) {
+      !pending_send_message_) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: replaying previously completed "
               "send_message op",
-              chand, calld);
+              chand, this);
     }
     if (replay_batch_data == nullptr) {
       replay_batch_data =
-          batch_data_create(elem, 1, true /* set_on_complete */);
+          SubchannelCallBatchData::Create(elem, 1, true /* set_on_complete */);
     }
-    add_retriable_send_message_op(elem, retry_state, replay_batch_data);
+    AddRetriableSendMessageOp(elem, retry_state, replay_batch_data);
   }
   // send_trailing_metadata.
   // Note that we only add this op if we have no more send_message ops
   // to start, since we can't send down any more send_message ops after
   // send_trailing_metadata.
-  if (calld->seen_send_trailing_metadata &&
-      retry_state->started_send_message_count == calld->send_messages.size() &&
+  if (seen_send_trailing_metadata_ &&
+      retry_state->started_send_message_count == send_messages_.size() &&
       !retry_state->started_send_trailing_metadata &&
-      !calld->pending_send_trailing_metadata) {
-    if (grpc_client_channel_call_trace.enabled()) {
+      !pending_send_trailing_metadata_) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: replaying previously completed "
               "send_trailing_metadata op",
-              chand, calld);
+              chand, this);
     }
     if (replay_batch_data == nullptr) {
       replay_batch_data =
-          batch_data_create(elem, 1, true /* set_on_complete */);
+          SubchannelCallBatchData::Create(elem, 1, true /* set_on_complete */);
     }
-    add_retriable_send_trailing_metadata_op(calld, retry_state,
-                                            replay_batch_data);
+    AddRetriableSendTrailingMetadataOp(retry_state, replay_batch_data);
   }
   return replay_batch_data;
 }
 
-// Adds subchannel batches for pending batches to batches, updating
-// *num_batches as needed.
-static void add_subchannel_batches_for_pending_batches(
-    grpc_call_element* elem, subchannel_call_retry_state* retry_state,
-    grpc_core::CallCombinerClosureList* closures) {
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  for (size_t i = 0; i < GPR_ARRAY_SIZE(calld->pending_batches); ++i) {
-    pending_batch* pending = &calld->pending_batches[i];
+void CallData::AddSubchannelBatchesForPendingBatches(
+    grpc_call_element* elem, SubchannelCallRetryState* retry_state,
+    CallCombinerClosureList* closures) {
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches_); ++i) {
+    PendingBatch* pending = &pending_batches_[i];
     grpc_transport_stream_op_batch* batch = pending->batch;
     if (batch == nullptr) continue;
     // Skip any batch that either (a) has already been started on this
@@ -2283,7 +2989,7 @@
     // send_message ops after send_trailing_metadata.
     if (batch->send_trailing_metadata &&
         (retry_state->started_send_message_count + batch->send_message <
-             calld->send_messages.size() ||
+             send_messages_.size() ||
          retry_state->started_send_trailing_metadata)) {
       continue;
     }
@@ -2298,7 +3004,7 @@
     if (batch->recv_trailing_metadata &&
         retry_state->started_recv_trailing_metadata) {
       // If we previously completed a recv_trailing_metadata op
-      // initiated by start_internal_recv_trailing_metadata(), use the
+      // initiated by StartInternalRecvTrailingMetadata(), use the
       // result of that instead of trying to re-start this op.
       if (GPR_UNLIKELY((retry_state->recv_trailing_metadata_internal_batch !=
                         nullptr))) {
@@ -2314,18 +3020,19 @@
               "re-executing recv_trailing_metadata_ready to propagate "
               "internally triggered result");
         } else {
-          batch_data_unref(retry_state->recv_trailing_metadata_internal_batch);
+          retry_state->recv_trailing_metadata_internal_batch->Unref();
         }
         retry_state->recv_trailing_metadata_internal_batch = nullptr;
       }
       continue;
     }
     // If we're not retrying, just send the batch as-is.
-    if (calld->method_params == nullptr ||
-        calld->method_params->retry_policy() == nullptr ||
-        calld->retry_committed) {
-      add_closure_for_subchannel_batch(elem, batch, closures);
-      pending_batch_clear(calld, pending);
+    if (method_params_ == nullptr ||
+        method_params_->retry_policy() == nullptr || retry_committed_) {
+      // TODO(roth) : We should probably call
+      // MaybeInjectRecvTrailingMetadataReadyForLoadBalancingPolicy here.
+      AddClosureForSubchannelBatch(elem, batch, closures);
+      PendingBatchClear(pending);
       continue;
     }
     // Create batch with the right number of callbacks.
@@ -2335,183 +3042,168 @@
     const int num_callbacks = has_send_ops + batch->recv_initial_metadata +
                               batch->recv_message +
                               batch->recv_trailing_metadata;
-    subchannel_batch_data* batch_data = batch_data_create(
+    SubchannelCallBatchData* batch_data = SubchannelCallBatchData::Create(
         elem, num_callbacks, has_send_ops /* set_on_complete */);
     // Cache send ops if needed.
-    maybe_cache_send_ops_for_batch(calld, pending);
+    MaybeCacheSendOpsForBatch(pending);
     // send_initial_metadata.
     if (batch->send_initial_metadata) {
-      add_retriable_send_initial_metadata_op(calld, retry_state, batch_data);
+      AddRetriableSendInitialMetadataOp(retry_state, batch_data);
     }
     // send_message.
     if (batch->send_message) {
-      add_retriable_send_message_op(elem, retry_state, batch_data);
+      AddRetriableSendMessageOp(elem, retry_state, batch_data);
     }
     // send_trailing_metadata.
     if (batch->send_trailing_metadata) {
-      add_retriable_send_trailing_metadata_op(calld, retry_state, batch_data);
+      AddRetriableSendTrailingMetadataOp(retry_state, batch_data);
     }
     // recv_initial_metadata.
     if (batch->recv_initial_metadata) {
       // recv_flags is only used on the server side.
       GPR_ASSERT(batch->payload->recv_initial_metadata.recv_flags == nullptr);
-      add_retriable_recv_initial_metadata_op(calld, retry_state, batch_data);
+      AddRetriableRecvInitialMetadataOp(retry_state, batch_data);
     }
     // recv_message.
     if (batch->recv_message) {
-      add_retriable_recv_message_op(calld, retry_state, batch_data);
+      AddRetriableRecvMessageOp(retry_state, batch_data);
     }
     // recv_trailing_metadata.
     if (batch->recv_trailing_metadata) {
-      add_retriable_recv_trailing_metadata_op(calld, retry_state, batch_data);
+      AddRetriableRecvTrailingMetadataOp(retry_state, batch_data);
     }
-    add_closure_for_subchannel_batch(elem, &batch_data->batch, closures);
+    AddClosureForSubchannelBatch(elem, &batch_data->batch, closures);
     // Track number of pending subchannel send batches.
     // If this is the first one, take a ref to the call stack.
     if (batch->send_initial_metadata || batch->send_message ||
         batch->send_trailing_metadata) {
-      if (calld->num_pending_retriable_subchannel_send_batches == 0) {
-        GRPC_CALL_STACK_REF(calld->owning_call, "subchannel_send_batches");
+      if (num_pending_retriable_subchannel_send_batches_ == 0) {
+        GRPC_CALL_STACK_REF(owning_call_, "subchannel_send_batches");
       }
-      ++calld->num_pending_retriable_subchannel_send_batches;
+      ++num_pending_retriable_subchannel_send_batches_;
     }
   }
 }
 
-// Constructs and starts whatever subchannel batches are needed on the
-// subchannel call.
-static void start_retriable_subchannel_batches(void* arg, grpc_error* ignored) {
+void CallData::StartRetriableSubchannelBatches(void* arg, grpc_error* ignored) {
   grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_call_trace.enabled()) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO, "chand=%p calld=%p: constructing retriable batches",
             chand, calld);
   }
-  subchannel_call_retry_state* retry_state =
-      static_cast<subchannel_call_retry_state*>(
-          calld->subchannel_call->GetParentData());
+  SubchannelCallRetryState* retry_state =
+      static_cast<SubchannelCallRetryState*>(
+          calld->subchannel_call_->GetParentData());
   // Construct list of closures to execute, one for each pending batch.
-  grpc_core::CallCombinerClosureList closures;
+  CallCombinerClosureList closures;
   // Replay previously-returned send_* ops if needed.
-  subchannel_batch_data* replay_batch_data =
-      maybe_create_subchannel_batch_for_replay(elem, retry_state);
+  SubchannelCallBatchData* replay_batch_data =
+      calld->MaybeCreateSubchannelBatchForReplay(elem, retry_state);
   if (replay_batch_data != nullptr) {
-    add_closure_for_subchannel_batch(elem, &replay_batch_data->batch,
-                                     &closures);
+    calld->AddClosureForSubchannelBatch(elem, &replay_batch_data->batch,
+                                        &closures);
     // Track number of pending subchannel send batches.
     // If this is the first one, take a ref to the call stack.
-    if (calld->num_pending_retriable_subchannel_send_batches == 0) {
-      GRPC_CALL_STACK_REF(calld->owning_call, "subchannel_send_batches");
+    if (calld->num_pending_retriable_subchannel_send_batches_ == 0) {
+      GRPC_CALL_STACK_REF(calld->owning_call_, "subchannel_send_batches");
     }
-    ++calld->num_pending_retriable_subchannel_send_batches;
+    ++calld->num_pending_retriable_subchannel_send_batches_;
   }
   // Now add pending batches.
-  add_subchannel_batches_for_pending_batches(elem, retry_state, &closures);
+  calld->AddSubchannelBatchesForPendingBatches(elem, retry_state, &closures);
   // Start batches on subchannel call.
-  if (grpc_client_channel_call_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_call_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: starting %" PRIuPTR
             " retriable batches on subchannel_call=%p",
-            chand, calld, closures.size(), calld->subchannel_call.get());
+            chand, calld, closures.size(), calld->subchannel_call_.get());
   }
   // Note: This will yield the call combiner.
-  closures.RunClosures(calld->call_combiner);
+  closures.RunClosures(calld->call_combiner_);
 }
 
 //
 // LB pick
 //
 
-static void create_subchannel_call(grpc_call_element* elem) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+void CallData::CreateSubchannelCall(grpc_call_element* elem) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   const size_t parent_data_size =
-      calld->enable_retries ? sizeof(subchannel_call_retry_state) : 0;
-  const grpc_core::ConnectedSubchannel::CallArgs call_args = {
-      calld->pollent,          // pollent
-      calld->path,             // path
-      calld->call_start_time,  // start_time
-      calld->deadline,         // deadline
-      calld->arena,            // arena
+      enable_retries_ ? sizeof(SubchannelCallRetryState) : 0;
+  const ConnectedSubchannel::CallArgs call_args = {
+      pollent_, path_, call_start_time_, deadline_, arena_,
       // TODO(roth): When we implement hedging support, we will probably
       // need to use a separate call context for each subchannel call.
-      calld->call_context,   // context
-      calld->call_combiner,  // call_combiner
-      parent_data_size       // parent_data_size
-  };
+      call_context_, call_combiner_, parent_data_size};
   grpc_error* error = GRPC_ERROR_NONE;
-  calld->subchannel_call =
-      calld->pick.pick.connected_subchannel->CreateCall(call_args, &error);
-  if (grpc_client_channel_routing_trace.enabled()) {
+  subchannel_call_ =
+      pick_.pick.connected_subchannel->CreateCall(call_args, &error);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
     gpr_log(GPR_INFO, "chand=%p calld=%p: create subchannel_call=%p: error=%s",
-            chand, calld, calld->subchannel_call.get(),
-            grpc_error_string(error));
+            chand, this, subchannel_call_.get(), grpc_error_string(error));
   }
   if (GPR_UNLIKELY(error != GRPC_ERROR_NONE)) {
-    pending_batches_fail(elem, error, yield_call_combiner);
+    PendingBatchesFail(elem, error, YieldCallCombiner);
   } else {
     if (parent_data_size > 0) {
-      new (calld->subchannel_call->GetParentData())
-          subchannel_call_retry_state(calld->call_context);
+      new (subchannel_call_->GetParentData())
+          SubchannelCallRetryState(call_context_);
     }
-    pending_batches_resume(elem);
+    PendingBatchesResume(elem);
   }
 }
 
-// Invoked when a pick is completed, on both success or failure.
-static void pick_done(void* arg, grpc_error* error) {
+void CallData::PickDone(void* arg, grpc_error* error) {
   grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
   if (error != GRPC_ERROR_NONE) {
-    if (grpc_client_channel_routing_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: failed to pick subchannel: error=%s", chand,
               calld, grpc_error_string(error));
     }
-    pending_batches_fail(elem, GRPC_ERROR_REF(error), yield_call_combiner);
+    calld->PendingBatchesFail(elem, GRPC_ERROR_REF(error), YieldCallCombiner);
     return;
   }
-  create_subchannel_call(elem);
+  calld->CreateSubchannelCall(elem);
 }
 
-namespace grpc_core {
-namespace {
-
 // A class to handle the call combiner cancellation callback for a
 // queued pick.
-class QueuedPickCanceller {
+class CallData::QueuedPickCanceller {
  public:
   explicit QueuedPickCanceller(grpc_call_element* elem) : elem_(elem) {
-    auto* calld = static_cast<call_data*>(elem->call_data);
-    auto* chand = static_cast<channel_data*>(elem->channel_data);
-    GRPC_CALL_STACK_REF(calld->owning_call, "QueuedPickCanceller");
+    auto* calld = static_cast<CallData*>(elem->call_data);
+    auto* chand = static_cast<ChannelData*>(elem->channel_data);
+    GRPC_CALL_STACK_REF(calld->owning_call_, "QueuedPickCanceller");
     GRPC_CLOSURE_INIT(&closure_, &CancelLocked, this,
-                      grpc_combiner_scheduler(chand->combiner));
-    grpc_call_combiner_set_notify_on_cancel(calld->call_combiner, &closure_);
+                      grpc_combiner_scheduler(chand->data_plane_combiner()));
+    calld->call_combiner_->SetNotifyOnCancel(&closure_);
   }
 
  private:
   static void CancelLocked(void* arg, grpc_error* error) {
     auto* self = static_cast<QueuedPickCanceller*>(arg);
-    auto* chand = static_cast<channel_data*>(self->elem_->channel_data);
-    auto* calld = static_cast<call_data*>(self->elem_->call_data);
-    if (grpc_client_channel_routing_trace.enabled()) {
+    auto* chand = static_cast<ChannelData*>(self->elem_->channel_data);
+    auto* calld = static_cast<CallData*>(self->elem_->call_data);
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
       gpr_log(GPR_INFO,
               "chand=%p calld=%p: cancelling queued pick: "
               "error=%s self=%p calld->pick_canceller=%p",
               chand, calld, grpc_error_string(error), self,
-              calld->pick_canceller);
+              calld->pick_canceller_);
     }
-    if (calld->pick_canceller == self && error != GRPC_ERROR_NONE) {
+    if (calld->pick_canceller_ == self && error != GRPC_ERROR_NONE) {
       // Remove pick from list of queued picks.
-      remove_call_from_queued_picks_locked(self->elem_);
+      calld->RemoveCallFromQueuedPicksLocked(self->elem_);
       // Fail pending batches on the call.
-      pending_batches_fail(self->elem_, GRPC_ERROR_REF(error),
-                           yield_call_combiner_if_pending_batches_found);
+      calld->PendingBatchesFail(self->elem_, GRPC_ERROR_REF(error),
+                                YieldCallCombinerIfPendingBatchesFound);
     }
-    GRPC_CALL_STACK_UNREF(calld->owning_call, "QueuedPickCanceller");
+    GRPC_CALL_STACK_UNREF(calld->owning_call_, "QueuedPickCanceller");
     Delete(self);
   }
 
@@ -2519,123 +3211,96 @@
   grpc_closure closure_;
 };
 
-}  // namespace
-}  // namespace grpc_core
-
-// Removes the call from the channel's list of queued picks.
-static void remove_call_from_queued_picks_locked(grpc_call_element* elem) {
-  auto* chand = static_cast<channel_data*>(elem->channel_data);
-  auto* calld = static_cast<call_data*>(elem->call_data);
-  for (QueuedPick** pick = &chand->queued_picks; *pick != nullptr;
-       pick = &(*pick)->next) {
-    if (*pick == &calld->pick) {
-      if (grpc_client_channel_routing_trace.enabled()) {
-        gpr_log(GPR_INFO, "chand=%p calld=%p: removing from queued picks list",
-                chand, calld);
-      }
-      calld->pick_queued = false;
-      *pick = calld->pick.next;
-      // Remove call's pollent from channel's interested_parties.
-      grpc_polling_entity_del_from_pollset_set(calld->pollent,
-                                               chand->interested_parties);
-      // Lame the call combiner canceller.
-      calld->pick_canceller = nullptr;
-      break;
-    }
+void CallData::RemoveCallFromQueuedPicksLocked(grpc_call_element* elem) {
+  auto* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
+    gpr_log(GPR_INFO, "chand=%p calld=%p: removing from queued picks list",
+            chand, this);
   }
+  chand->RemoveQueuedPick(&pick_, pollent_);
+  pick_queued_ = false;
+  // Lame the call combiner canceller.
+  pick_canceller_ = nullptr;
 }
 
-// Adds the call to the channel's list of queued picks.
-static void add_call_to_queued_picks_locked(grpc_call_element* elem) {
-  auto* chand = static_cast<channel_data*>(elem->channel_data);
-  auto* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_routing_trace.enabled()) {
+void CallData::AddCallToQueuedPicksLocked(grpc_call_element* elem) {
+  auto* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
     gpr_log(GPR_INFO, "chand=%p calld=%p: adding to queued picks list", chand,
-            calld);
+            this);
   }
-  calld->pick_queued = true;
-  // Add call to queued picks list.
-  calld->pick.elem = elem;
-  calld->pick.next = chand->queued_picks;
-  chand->queued_picks = &calld->pick;
-  // Add call's pollent to channel's interested_parties, so that I/O
-  // can be done under the call's CQ.
-  grpc_polling_entity_add_to_pollset_set(calld->pollent,
-                                         chand->interested_parties);
+  pick_queued_ = true;
+  pick_.elem = elem;
+  chand->AddQueuedPick(&pick_, pollent_);
   // Register call combiner cancellation callback.
-  calld->pick_canceller = grpc_core::New<grpc_core::QueuedPickCanceller>(elem);
+  pick_canceller_ = New<QueuedPickCanceller>(elem);
 }
 
-// Applies service config to the call.  Must be invoked once we know
-// that the resolver has returned results to the channel.
-static void apply_service_config_to_call_locked(grpc_call_element* elem) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (grpc_client_channel_routing_trace.enabled()) {
+void CallData::ApplyServiceConfigToCallLocked(grpc_call_element* elem) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
     gpr_log(GPR_INFO, "chand=%p calld=%p: applying service config to call",
-            chand, calld);
+            chand, this);
   }
-  if (chand->retry_throttle_data != nullptr) {
-    calld->retry_throttle_data = chand->retry_throttle_data->Ref();
+  // Store a ref to the service_config in service_config_call_data_. Also, save
+  // a pointer to this in the call_context so that all future filters can access
+  // it.
+  service_config_call_data_ =
+      ServiceConfig::CallData(chand->service_config(), path_);
+  if (service_config_call_data_.service_config() != nullptr) {
+    call_context_[GRPC_SERVICE_CONFIG_CALL_DATA].value =
+        &service_config_call_data_;
+    method_params_ = static_cast<ClientChannelMethodParsedObject*>(
+        service_config_call_data_.GetMethodParsedObject(
+            internal::ClientChannelServiceConfigParser::ParserIndex()));
   }
-  if (chand->method_params_table != nullptr) {
-    calld->method_params = grpc_core::ServiceConfig::MethodConfigTableLookup(
-        *chand->method_params_table, calld->path);
-    if (calld->method_params != nullptr) {
-      // If the deadline from the service config is shorter than the one
-      // from the client API, reset the deadline timer.
-      if (chand->deadline_checking_enabled &&
-          calld->method_params->timeout() != 0) {
-        const grpc_millis per_method_deadline =
-            grpc_timespec_to_millis_round_up(calld->call_start_time) +
-            calld->method_params->timeout();
-        if (per_method_deadline < calld->deadline) {
-          calld->deadline = per_method_deadline;
-          grpc_deadline_state_reset(elem, calld->deadline);
-        }
+  retry_throttle_data_ = chand->retry_throttle_data();
+  if (method_params_ != nullptr) {
+    // If the deadline from the service config is shorter than the one
+    // from the client API, reset the deadline timer.
+    if (chand->deadline_checking_enabled() && method_params_->timeout() != 0) {
+      const grpc_millis per_method_deadline =
+          grpc_timespec_to_millis_round_up(call_start_time_) +
+          method_params_->timeout();
+      if (per_method_deadline < deadline_) {
+        deadline_ = per_method_deadline;
+        grpc_deadline_state_reset(elem, deadline_);
       }
-      // If the service config set wait_for_ready and the application
-      // did not explicitly set it, use the value from the service config.
-      uint32_t* send_initial_metadata_flags =
-          &calld->pending_batches[0]
-               .batch->payload->send_initial_metadata
-               .send_initial_metadata_flags;
-      if (GPR_UNLIKELY(
-              calld->method_params->wait_for_ready() !=
-                  ClientChannelMethodParams::WAIT_FOR_READY_UNSET &&
-              !(*send_initial_metadata_flags &
-                GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET))) {
-        if (calld->method_params->wait_for_ready() ==
-            ClientChannelMethodParams::WAIT_FOR_READY_TRUE) {
-          *send_initial_metadata_flags |= GRPC_INITIAL_METADATA_WAIT_FOR_READY;
-        } else {
-          *send_initial_metadata_flags &= ~GRPC_INITIAL_METADATA_WAIT_FOR_READY;
-        }
+    }
+    // If the service config set wait_for_ready and the application
+    // did not explicitly set it, use the value from the service config.
+    uint32_t* send_initial_metadata_flags =
+        &pending_batches_[0]
+             .batch->payload->send_initial_metadata.send_initial_metadata_flags;
+    if (method_params_->wait_for_ready().has_value() &&
+        !(*send_initial_metadata_flags &
+          GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET)) {
+      if (method_params_->wait_for_ready().value()) {
+        *send_initial_metadata_flags |= GRPC_INITIAL_METADATA_WAIT_FOR_READY;
+      } else {
+        *send_initial_metadata_flags &= ~GRPC_INITIAL_METADATA_WAIT_FOR_READY;
       }
     }
   }
   // If no retry policy, disable retries.
   // TODO(roth): Remove this when adding support for transparent retries.
-  if (calld->method_params == nullptr ||
-      calld->method_params->retry_policy() == nullptr) {
-    calld->enable_retries = false;
+  if (method_params_ == nullptr || method_params_->retry_policy() == nullptr) {
+    enable_retries_ = false;
   }
 }
 
-// Invoked once resolver results are available.
-static void maybe_apply_service_config_to_call_locked(grpc_call_element* elem) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
+void CallData::MaybeApplyServiceConfigToCallLocked(grpc_call_element* elem) {
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
   // Apply service config data to the call only once, and only if the
   // channel has the data available.
-  if (GPR_LIKELY(chand->have_service_config &&
-                 !calld->service_config_applied)) {
-    calld->service_config_applied = true;
-    apply_service_config_to_call_locked(elem);
+  if (GPR_LIKELY(chand->received_service_config_data() &&
+                 !service_config_applied_)) {
+    service_config_applied_ = true;
+    ApplyServiceConfigToCallLocked(elem);
   }
 }
 
-static const char* pick_result_name(LoadBalancingPolicy::PickResult result) {
+const char* PickResultName(LoadBalancingPolicy::PickResult result) {
   switch (result) {
     case LoadBalancingPolicy::PICK_COMPLETE:
       return "COMPLETE";
@@ -2647,56 +3312,57 @@
   GPR_UNREACHABLE_CODE(return "UNKNOWN");
 }
 
-static void start_pick_locked(void* arg, grpc_error* error) {
+void CallData::StartPickLocked(void* arg, grpc_error* error) {
   grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  GPR_ASSERT(calld->pick.pick.connected_subchannel == nullptr);
-  GPR_ASSERT(calld->subchannel_call == nullptr);
+  CallData* calld = static_cast<CallData*>(elem->call_data);
+  ChannelData* chand = static_cast<ChannelData*>(elem->channel_data);
+  GPR_ASSERT(calld->pick_.pick.connected_subchannel == nullptr);
+  GPR_ASSERT(calld->subchannel_call_ == nullptr);
   // If this is a retry, use the send_initial_metadata payload that
   // we've cached; otherwise, use the pending batch.  The
   // send_initial_metadata batch will be the first pending batch in the
-  // list, as set by get_batch_index() above.
+  // list, as set by GetBatchIndex() above.
   // TODO(roth): What if the LB policy needs to add something to the
   // call's initial metadata, and then there's a retry?  We don't want
   // the new metadata to be added twice.  We might need to somehow
   // allocate the subchannel batch earlier so that we can give the
   // subchannel's copy of the metadata batch (which is copied for each
   // attempt) to the LB policy instead the one from the parent channel.
-  calld->pick.pick.initial_metadata =
-      calld->seen_send_initial_metadata
-          ? &calld->send_initial_metadata
-          : calld->pending_batches[0]
+  calld->pick_.pick.initial_metadata =
+      calld->seen_send_initial_metadata_
+          ? &calld->send_initial_metadata_
+          : calld->pending_batches_[0]
                 .batch->payload->send_initial_metadata.send_initial_metadata;
   uint32_t* send_initial_metadata_flags =
-      calld->seen_send_initial_metadata
-          ? &calld->send_initial_metadata_flags
-          : &calld->pending_batches[0]
+      calld->seen_send_initial_metadata_
+          ? &calld->send_initial_metadata_flags_
+          : &calld->pending_batches_[0]
                  .batch->payload->send_initial_metadata
                  .send_initial_metadata_flags;
   // Apply service config to call if needed.
-  maybe_apply_service_config_to_call_locked(elem);
-  // When done, we schedule this closure to leave the channel combiner.
-  GRPC_CLOSURE_INIT(&calld->pick_closure, pick_done, elem,
+  calld->MaybeApplyServiceConfigToCallLocked(elem);
+  // When done, we schedule this closure to leave the data plane combiner.
+  GRPC_CLOSURE_INIT(&calld->pick_closure_, PickDone, elem,
                     grpc_schedule_on_exec_ctx);
   // Attempt pick.
   error = GRPC_ERROR_NONE;
-  auto pick_result = chand->picker->Pick(&calld->pick.pick, &error);
-  if (grpc_client_channel_routing_trace.enabled()) {
+  auto pick_result = chand->picker()->Pick(&calld->pick_.pick, &error);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) {
     gpr_log(GPR_INFO,
             "chand=%p calld=%p: LB pick returned %s (connected_subchannel=%p, "
             "error=%s)",
-            chand, calld, pick_result_name(pick_result),
-            calld->pick.pick.connected_subchannel.get(),
+            chand, calld, PickResultName(pick_result),
+            calld->pick_.pick.connected_subchannel.get(),
             grpc_error_string(error));
   }
   switch (pick_result) {
-    case LoadBalancingPolicy::PICK_TRANSIENT_FAILURE:
+    case LoadBalancingPolicy::PICK_TRANSIENT_FAILURE: {
       // If we're shutting down, fail all RPCs.
-      if (chand->disconnect_error != GRPC_ERROR_NONE) {
+      grpc_error* disconnect_error = chand->disconnect_error();
+      if (disconnect_error != GRPC_ERROR_NONE) {
         GRPC_ERROR_UNREF(error);
-        GRPC_CLOSURE_SCHED(&calld->pick_closure,
-                           GRPC_ERROR_REF(chand->disconnect_error));
+        GRPC_CLOSURE_SCHED(&calld->pick_closure_,
+                           GRPC_ERROR_REF(disconnect_error));
         break;
       }
       // If wait_for_ready is false, then the error indicates the RPC
@@ -2705,355 +3371,100 @@
            GRPC_INITIAL_METADATA_WAIT_FOR_READY) == 0) {
         // Retry if appropriate; otherwise, fail.
         grpc_status_code status = GRPC_STATUS_OK;
-        grpc_error_get_status(error, calld->deadline, &status, nullptr, nullptr,
-                              nullptr);
-        if (!calld->enable_retries ||
-            !maybe_retry(elem, nullptr /* batch_data */, status,
-                         nullptr /* server_pushback_md */)) {
+        grpc_error_get_status(error, calld->deadline_, &status, nullptr,
+                              nullptr, nullptr);
+        if (!calld->enable_retries_ ||
+            !calld->MaybeRetry(elem, nullptr /* batch_data */, status,
+                               nullptr /* server_pushback_md */)) {
           grpc_error* new_error =
               GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
-                  "Failed to create subchannel", &error, 1);
+                  "Failed to pick subchannel", &error, 1);
           GRPC_ERROR_UNREF(error);
-          GRPC_CLOSURE_SCHED(&calld->pick_closure, new_error);
+          GRPC_CLOSURE_SCHED(&calld->pick_closure_, new_error);
         }
-        if (calld->pick_queued) remove_call_from_queued_picks_locked(elem);
+        if (calld->pick_queued_) calld->RemoveCallFromQueuedPicksLocked(elem);
         break;
       }
       // If wait_for_ready is true, then queue to retry when we get a new
       // picker.
       GRPC_ERROR_UNREF(error);
-      // Fallthrough
+    }
+    // Fallthrough
     case LoadBalancingPolicy::PICK_QUEUE:
-      if (!calld->pick_queued) add_call_to_queued_picks_locked(elem);
+      if (!calld->pick_queued_) calld->AddCallToQueuedPicksLocked(elem);
       break;
     default:  // PICK_COMPLETE
       // Handle drops.
-      if (GPR_UNLIKELY(calld->pick.pick.connected_subchannel == nullptr)) {
+      if (GPR_UNLIKELY(calld->pick_.pick.connected_subchannel == nullptr)) {
         error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
             "Call dropped by load balancing policy");
       }
-      GRPC_CLOSURE_SCHED(&calld->pick_closure, error);
-      if (calld->pick_queued) remove_call_from_queued_picks_locked(elem);
+      GRPC_CLOSURE_SCHED(&calld->pick_closure_, error);
+      if (calld->pick_queued_) calld->RemoveCallFromQueuedPicksLocked(elem);
   }
 }
 
-//
-// filter call vtable functions
-//
-
-static void cc_start_transport_stream_op_batch(
-    grpc_call_element* elem, grpc_transport_stream_op_batch* batch) {
-  GPR_TIMER_SCOPE("cc_start_transport_stream_op_batch", 0);
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  if (GPR_LIKELY(chand->deadline_checking_enabled)) {
-    grpc_deadline_state_client_start_transport_stream_op_batch(elem, batch);
-  }
-  // If we've previously been cancelled, immediately fail any new batches.
-  if (GPR_UNLIKELY(calld->cancel_error != GRPC_ERROR_NONE)) {
-    if (grpc_client_channel_call_trace.enabled()) {
-      gpr_log(GPR_INFO, "chand=%p calld=%p: failing batch with error: %s",
-              chand, calld, grpc_error_string(calld->cancel_error));
-    }
-    // Note: This will release the call combiner.
-    grpc_transport_stream_op_batch_finish_with_failure(
-        batch, GRPC_ERROR_REF(calld->cancel_error), calld->call_combiner);
-    return;
-  }
-  // Handle cancellation.
-  if (GPR_UNLIKELY(batch->cancel_stream)) {
-    // Stash a copy of cancel_error in our call data, so that we can use
-    // it for subsequent operations.  This ensures that if the call is
-    // cancelled before any batches are passed down (e.g., if the deadline
-    // is in the past when the call starts), we can return the right
-    // error to the caller when the first batch does get passed down.
-    GRPC_ERROR_UNREF(calld->cancel_error);
-    calld->cancel_error =
-        GRPC_ERROR_REF(batch->payload->cancel_stream.cancel_error);
-    if (grpc_client_channel_call_trace.enabled()) {
-      gpr_log(GPR_INFO, "chand=%p calld=%p: recording cancel_error=%s", chand,
-              calld, grpc_error_string(calld->cancel_error));
-    }
-    // If we do not have a subchannel call (i.e., a pick has not yet
-    // been started), fail all pending batches.  Otherwise, send the
-    // cancellation down to the subchannel call.
-    if (calld->subchannel_call == nullptr) {
-      // TODO(roth): If there is a pending retry callback, do we need to
-      // cancel it here?
-      pending_batches_fail(elem, GRPC_ERROR_REF(calld->cancel_error),
-                           no_yield_call_combiner);
-      // Note: This will release the call combiner.
-      grpc_transport_stream_op_batch_finish_with_failure(
-          batch, GRPC_ERROR_REF(calld->cancel_error), calld->call_combiner);
-    } else {
-      // Note: This will release the call combiner.
-      calld->subchannel_call->StartTransportStreamOpBatch(batch);
-    }
-    return;
-  }
-  // Add the batch to the pending list.
-  pending_batches_add(elem, batch);
-  // Check if we've already gotten a subchannel call.
-  // Note that once we have completed the pick, we do not need to enter
-  // the channel combiner, which is more efficient (especially for
-  // streaming calls).
-  if (calld->subchannel_call != nullptr) {
-    if (grpc_client_channel_call_trace.enabled()) {
-      gpr_log(GPR_INFO,
-              "chand=%p calld=%p: starting batch on subchannel_call=%p", chand,
-              calld, calld->subchannel_call.get());
-    }
-    pending_batches_resume(elem);
-    return;
-  }
-  // We do not yet have a subchannel call.
-  // For batches containing a send_initial_metadata op, enter the channel
-  // combiner to start a pick.
-  if (GPR_LIKELY(batch->send_initial_metadata)) {
-    if (grpc_client_channel_call_trace.enabled()) {
-      gpr_log(GPR_INFO, "chand=%p calld=%p: entering client_channel combiner",
-              chand, calld);
-    }
-    GRPC_CLOSURE_SCHED(
-        GRPC_CLOSURE_INIT(&batch->handler_private.closure, start_pick_locked,
-                          elem, grpc_combiner_scheduler(chand->combiner)),
-        GRPC_ERROR_NONE);
-  } else {
-    // For all other batches, release the call combiner.
-    if (grpc_client_channel_call_trace.enabled()) {
-      gpr_log(GPR_INFO,
-              "chand=%p calld=%p: saved batch, yielding call combiner", chand,
-              calld);
-    }
-    GRPC_CALL_COMBINER_STOP(calld->call_combiner,
-                            "batch does not include send_initial_metadata");
-  }
-}
-
-/* Constructor for call_data */
-static grpc_error* cc_init_call_elem(grpc_call_element* elem,
-                                     const grpc_call_element_args* args) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  new (elem->call_data) call_data(elem, *chand, *args);
-  return GRPC_ERROR_NONE;
-}
-
-/* Destructor for call_data */
-static void cc_destroy_call_elem(grpc_call_element* elem,
-                                 const grpc_call_final_info* final_info,
-                                 grpc_closure* then_schedule_closure) {
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  if (GPR_LIKELY(calld->subchannel_call != nullptr)) {
-    calld->subchannel_call->SetAfterCallStackDestroy(then_schedule_closure);
-    then_schedule_closure = nullptr;
-  }
-  calld->~call_data();
-  GRPC_CLOSURE_SCHED(then_schedule_closure, GRPC_ERROR_NONE);
-}
-
-static void cc_set_pollset_or_pollset_set(grpc_call_element* elem,
-                                          grpc_polling_entity* pollent) {
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  calld->pollent = pollent;
-}
+}  // namespace
+}  // namespace grpc_core
 
 /*************************************************************************
  * EXPORTED SYMBOLS
  */
 
+using grpc_core::CallData;
+using grpc_core::ChannelData;
+
 const grpc_channel_filter grpc_client_channel_filter = {
-    cc_start_transport_stream_op_batch,
-    cc_start_transport_op,
-    sizeof(call_data),
-    cc_init_call_elem,
-    cc_set_pollset_or_pollset_set,
-    cc_destroy_call_elem,
-    sizeof(channel_data),
-    cc_init_channel_elem,
-    cc_destroy_channel_elem,
-    cc_get_channel_info,
+    CallData::StartTransportStreamOpBatch,
+    ChannelData::StartTransportOp,
+    sizeof(CallData),
+    CallData::Init,
+    CallData::SetPollent,
+    CallData::Destroy,
+    sizeof(ChannelData),
+    ChannelData::Init,
+    ChannelData::Destroy,
+    ChannelData::GetChannelInfo,
     "client-channel",
 };
 
 void grpc_client_channel_set_channelz_node(
     grpc_channel_element* elem, grpc_core::channelz::ClientChannelNode* node) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  chand->channelz_node = node;
-  chand->resolving_lb_policy->set_channelz_node(node->Ref());
+  auto* chand = static_cast<ChannelData*>(elem->channel_data);
+  chand->set_channelz_node(node);
 }
 
 void grpc_client_channel_populate_child_refs(
     grpc_channel_element* elem,
     grpc_core::channelz::ChildRefsList* child_subchannels,
     grpc_core::channelz::ChildRefsList* child_channels) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  if (chand->resolving_lb_policy != nullptr) {
-    chand->resolving_lb_policy->FillChildRefsForChannelz(child_subchannels,
-                                                         child_channels);
-  }
-}
-
-static void try_to_connect_locked(void* arg, grpc_error* error_ignored) {
-  channel_data* chand = static_cast<channel_data*>(arg);
-  chand->resolving_lb_policy->ExitIdleLocked();
-  GRPC_CHANNEL_STACK_UNREF(chand->owning_stack, "try_to_connect");
+  auto* chand = static_cast<ChannelData*>(elem->channel_data);
+  chand->FillChildRefsForChannelz(child_subchannels, child_channels);
 }
 
 grpc_connectivity_state grpc_client_channel_check_connectivity_state(
     grpc_channel_element* elem, int try_to_connect) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  grpc_connectivity_state out =
-      grpc_connectivity_state_check(&chand->state_tracker);
-  if (out == GRPC_CHANNEL_IDLE && try_to_connect) {
-    GRPC_CHANNEL_STACK_REF(chand->owning_stack, "try_to_connect");
-    GRPC_CLOSURE_SCHED(
-        GRPC_CLOSURE_CREATE(try_to_connect_locked, chand,
-                            grpc_combiner_scheduler(chand->combiner)),
-        GRPC_ERROR_NONE);
-  }
-  return out;
-}
-
-typedef struct external_connectivity_watcher {
-  channel_data* chand;
-  grpc_polling_entity pollent;
-  grpc_closure* on_complete;
-  grpc_closure* watcher_timer_init;
-  grpc_connectivity_state* state;
-  grpc_closure my_closure;
-  struct external_connectivity_watcher* next;
-} external_connectivity_watcher;
-
-static external_connectivity_watcher* lookup_external_connectivity_watcher(
-    channel_data* chand, grpc_closure* on_complete) {
-  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
-  external_connectivity_watcher* w =
-      chand->external_connectivity_watcher_list_head;
-  while (w != nullptr && w->on_complete != on_complete) {
-    w = w->next;
-  }
-  gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
-  return w;
-}
-
-static void external_connectivity_watcher_list_append(
-    channel_data* chand, external_connectivity_watcher* w) {
-  GPR_ASSERT(!lookup_external_connectivity_watcher(chand, w->on_complete));
-
-  gpr_mu_lock(&w->chand->external_connectivity_watcher_list_mu);
-  GPR_ASSERT(!w->next);
-  w->next = chand->external_connectivity_watcher_list_head;
-  chand->external_connectivity_watcher_list_head = w;
-  gpr_mu_unlock(&w->chand->external_connectivity_watcher_list_mu);
-}
-
-static void external_connectivity_watcher_list_remove(
-    channel_data* chand, external_connectivity_watcher* to_remove) {
-  GPR_ASSERT(
-      lookup_external_connectivity_watcher(chand, to_remove->on_complete));
-  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
-  if (to_remove == chand->external_connectivity_watcher_list_head) {
-    chand->external_connectivity_watcher_list_head = to_remove->next;
-    gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
-    return;
-  }
-  external_connectivity_watcher* w =
-      chand->external_connectivity_watcher_list_head;
-  while (w != nullptr) {
-    if (w->next == to_remove) {
-      w->next = w->next->next;
-      gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
-      return;
-    }
-    w = w->next;
-  }
-  GPR_UNREACHABLE_CODE(return );
+  auto* chand = static_cast<ChannelData*>(elem->channel_data);
+  return chand->CheckConnectivityState(try_to_connect);
 }
 
 int grpc_client_channel_num_external_connectivity_watchers(
     grpc_channel_element* elem) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  int count = 0;
-
-  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
-  external_connectivity_watcher* w =
-      chand->external_connectivity_watcher_list_head;
-  while (w != nullptr) {
-    count++;
-    w = w->next;
-  }
-  gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
-
-  return count;
-}
-
-static void on_external_watch_complete_locked(void* arg, grpc_error* error) {
-  external_connectivity_watcher* w =
-      static_cast<external_connectivity_watcher*>(arg);
-  grpc_closure* follow_up = w->on_complete;
-  grpc_polling_entity_del_from_pollset_set(&w->pollent,
-                                           w->chand->interested_parties);
-  GRPC_CHANNEL_STACK_UNREF(w->chand->owning_stack,
-                           "external_connectivity_watcher");
-  external_connectivity_watcher_list_remove(w->chand, w);
-  gpr_free(w);
-  GRPC_CLOSURE_SCHED(follow_up, GRPC_ERROR_REF(error));
-}
-
-static void watch_connectivity_state_locked(void* arg,
-                                            grpc_error* error_ignored) {
-  external_connectivity_watcher* w =
-      static_cast<external_connectivity_watcher*>(arg);
-  external_connectivity_watcher* found = nullptr;
-  if (w->state != nullptr) {
-    external_connectivity_watcher_list_append(w->chand, w);
-    // An assumption is being made that the closure is scheduled on the exec ctx
-    // scheduler and that GRPC_CLOSURE_RUN would run the closure immediately.
-    GRPC_CLOSURE_RUN(w->watcher_timer_init, GRPC_ERROR_NONE);
-    GRPC_CLOSURE_INIT(&w->my_closure, on_external_watch_complete_locked, w,
-                      grpc_combiner_scheduler(w->chand->combiner));
-    grpc_connectivity_state_notify_on_state_change(&w->chand->state_tracker,
-                                                   w->state, &w->my_closure);
-  } else {
-    GPR_ASSERT(w->watcher_timer_init == nullptr);
-    found = lookup_external_connectivity_watcher(w->chand, w->on_complete);
-    if (found) {
-      GPR_ASSERT(found->on_complete == w->on_complete);
-      grpc_connectivity_state_notify_on_state_change(
-          &found->chand->state_tracker, nullptr, &found->my_closure);
-    }
-    grpc_polling_entity_del_from_pollset_set(&w->pollent,
-                                             w->chand->interested_parties);
-    GRPC_CHANNEL_STACK_UNREF(w->chand->owning_stack,
-                             "external_connectivity_watcher");
-    gpr_free(w);
-  }
+  auto* chand = static_cast<ChannelData*>(elem->channel_data);
+  return chand->NumExternalConnectivityWatchers();
 }
 
 void grpc_client_channel_watch_connectivity_state(
     grpc_channel_element* elem, grpc_polling_entity pollent,
     grpc_connectivity_state* state, grpc_closure* closure,
     grpc_closure* watcher_timer_init) {
-  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  external_connectivity_watcher* w =
-      static_cast<external_connectivity_watcher*>(gpr_zalloc(sizeof(*w)));
-  w->chand = chand;
-  w->pollent = pollent;
-  w->on_complete = closure;
-  w->state = state;
-  w->watcher_timer_init = watcher_timer_init;
-  grpc_polling_entity_add_to_pollset_set(&w->pollent,
-                                         chand->interested_parties);
-  GRPC_CHANNEL_STACK_REF(w->chand->owning_stack,
-                         "external_connectivity_watcher");
-  GRPC_CLOSURE_SCHED(
-      GRPC_CLOSURE_INIT(&w->my_closure, watch_connectivity_state_locked, w,
-                        grpc_combiner_scheduler(chand->combiner)),
-      GRPC_ERROR_NONE);
+  auto* chand = static_cast<ChannelData*>(elem->channel_data);
+  return chand->AddExternalConnectivityWatcher(pollent, state, closure,
+                                               watcher_timer_init);
 }
 
 grpc_core::RefCountedPtr<grpc_core::SubchannelCall>
 grpc_client_channel_get_subchannel_call(grpc_call_element* elem) {
-  call_data* calld = static_cast<call_data*>(elem->call_data);
-  return calld->subchannel_call;
+  auto* calld = static_cast<CallData*>(elem->call_data);
+  return calld->subchannel_call();
 }
diff --git a/src/core/ext/filters/client_channel/client_channel_channelz.cc b/src/core/ext/filters/client_channel/client_channel_channelz.cc
index 76c5a78..a7a47e9 100644
--- a/src/core/ext/filters/client_channel/client_channel_channelz.cc
+++ b/src/core/ext/filters/client_channel/client_channel_channelz.cc
@@ -49,8 +49,8 @@
     : ChannelNode(channel, channel_tracer_max_nodes, is_top_level_channel) {
   client_channel_ =
       grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel));
-  grpc_client_channel_set_channelz_node(client_channel_, this);
   GPR_ASSERT(client_channel_->filter == &grpc_client_channel_filter);
+  grpc_client_channel_set_channelz_node(client_channel_, this);
 }
 
 void ClientChannelNode::PopulateConnectivityState(grpc_json* json) {
@@ -127,8 +127,7 @@
   if (subchannel_ == nullptr) {
     state = GRPC_CHANNEL_SHUTDOWN;
   } else {
-    state = subchannel_->CheckConnectivity(nullptr,
-                                           true /* inhibit_health_checking */);
+    state = subchannel_->CheckConnectivity(true /* inhibit_health_checking */);
   }
   json = grpc_json_create_child(nullptr, json, "state", nullptr,
                                 GRPC_JSON_OBJECT, false);
diff --git a/src/core/ext/filters/client_channel/client_channel_plugin.cc b/src/core/ext/filters/client_channel/client_channel_plugin.cc
index 2031ab4..e564df8 100644
--- a/src/core/ext/filters/client_channel/client_channel_plugin.cc
+++ b/src/core/ext/filters/client_channel/client_channel_plugin.cc
@@ -32,6 +32,7 @@
 #include "src/core/ext/filters/client_channel/lb_policy_registry.h"
 #include "src/core/ext/filters/client_channel/proxy_mapper_registry.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
+#include "src/core/ext/filters/client_channel/resolver_result_parsing.h"
 #include "src/core/ext/filters/client_channel/retry_throttle.h"
 #include "src/core/lib/surface/channel_init.h"
 
@@ -49,6 +50,8 @@
 }
 
 void grpc_client_channel_init(void) {
+  grpc_core::ServiceConfig::Init();
+  grpc_core::internal::ClientChannelServiceConfigParser::Register();
   grpc_core::LoadBalancingPolicyRegistry::Builder::InitRegistry();
   grpc_core::ResolverRegistry::Builder::InitRegistry();
   grpc_core::internal::ServerRetryThrottleMap::Init();
@@ -68,4 +71,5 @@
   grpc_core::internal::ServerRetryThrottleMap::Shutdown();
   grpc_core::ResolverRegistry::Builder::ShutdownRegistry();
   grpc_core::LoadBalancingPolicyRegistry::Builder::ShutdownRegistry();
+  grpc_core::ServiceConfig::Shutdown();
 }
diff --git a/src/core/ext/filters/client_channel/health/health_check_client.cc b/src/core/ext/filters/client_channel/health/health_check_client.cc
index 84a7f35..bfac773 100644
--- a/src/core/ext/filters/client_channel/health/health_check_client.cc
+++ b/src/core/ext/filters/client_channel/health/health_check_client.cc
@@ -27,7 +27,7 @@
 #include "pb_encode.h"
 #include "src/core/ext/filters/client_channel/health/health.pb.h"
 #include "src/core/lib/debug/trace.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/transport/error_utils.h"
 #include "src/core/lib/transport/status_metadata.h"
@@ -37,11 +37,10 @@
 #define HEALTH_CHECK_RECONNECT_MAX_BACKOFF_SECONDS 120
 #define HEALTH_CHECK_RECONNECT_JITTER 0.2
 
-grpc_core::TraceFlag grpc_health_check_client_trace(false,
-                                                    "health_check_client");
-
 namespace grpc_core {
 
+TraceFlag grpc_health_check_client_trace(false, "health_check_client");
+
 //
 // HealthCheckClient
 //
@@ -50,7 +49,7 @@
     const char* service_name,
     RefCountedPtr<ConnectedSubchannel> connected_subchannel,
     grpc_pollset_set* interested_parties,
-    grpc_core::RefCountedPtr<grpc_core::channelz::SubchannelNode> channelz_node)
+    RefCountedPtr<channelz::SubchannelNode> channelz_node)
     : InternallyRefCounted<HealthCheckClient>(&grpc_health_check_client_trace),
       service_name_(service_name),
       connected_subchannel_(std::move(connected_subchannel)),
@@ -64,21 +63,19 @@
               .set_jitter(HEALTH_CHECK_RECONNECT_JITTER)
               .set_max_backoff(HEALTH_CHECK_RECONNECT_MAX_BACKOFF_SECONDS *
                                1000)) {
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO, "created HealthCheckClient %p", this);
   }
   GRPC_CLOSURE_INIT(&retry_timer_callback_, OnRetryTimer, this,
                     grpc_schedule_on_exec_ctx);
-  gpr_mu_init(&mu_);
   StartCall();
 }
 
 HealthCheckClient::~HealthCheckClient() {
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO, "destroying HealthCheckClient %p", this);
   }
   GRPC_ERROR_UNREF(error_);
-  gpr_mu_destroy(&mu_);
 }
 
 void HealthCheckClient::NotifyOnHealthChange(grpc_connectivity_state* state,
@@ -102,7 +99,7 @@
 
 void HealthCheckClient::SetHealthStatusLocked(grpc_connectivity_state state,
                                               grpc_error* error) {
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO, "HealthCheckClient %p: setting state=%d error=%s", this,
             state, grpc_error_string(error));
   }
@@ -118,7 +115,7 @@
 }
 
 void HealthCheckClient::Orphan() {
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO, "HealthCheckClient %p: shutting down", this);
   }
   {
@@ -148,7 +145,7 @@
   GPR_ASSERT(call_state_ == nullptr);
   SetHealthStatusLocked(GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE);
   call_state_ = MakeOrphanable<CallState>(Ref(), interested_parties_);
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO, "HealthCheckClient %p: created CallState %p", this,
             call_state_.get());
   }
@@ -162,7 +159,7 @@
       GRPC_ERROR_CREATE_FROM_STATIC_STRING(
           "health check call failed; will retry after backoff"));
   grpc_millis next_try = retry_backoff_.NextAttemptTime();
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO, "HealthCheckClient %p: health check call lost...", this);
     grpc_millis timeout = next_try - ExecCtx::Get()->Now();
     if (timeout > 0) {
@@ -187,7 +184,7 @@
     self->retry_timer_callback_pending_ = false;
     if (!self->shutting_down_ && error == GRPC_ERROR_NONE &&
         self->call_state_ == nullptr) {
-      if (grpc_health_check_client_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
         gpr_log(GPR_INFO, "HealthCheckClient %p: restarting health check call",
                 self);
       }
@@ -280,24 +277,17 @@
 HealthCheckClient::CallState::CallState(
     RefCountedPtr<HealthCheckClient> health_check_client,
     grpc_pollset_set* interested_parties)
-    : InternallyRefCounted<CallState>(&grpc_health_check_client_trace),
-      health_check_client_(std::move(health_check_client)),
+    : health_check_client_(std::move(health_check_client)),
       pollent_(grpc_polling_entity_create_from_pollset_set(interested_parties)),
-      arena_(gpr_arena_create(health_check_client_->connected_subchannel_
-                                  ->GetInitialCallSizeEstimate(0))),
-      payload_(context_) {
-  grpc_call_combiner_init(&call_combiner_);
-  gpr_atm_rel_store(&seen_response_, static_cast<gpr_atm>(0));
-}
+      arena_(Arena::Create(health_check_client_->connected_subchannel_
+                               ->GetInitialCallSizeEstimate(0))),
+      payload_(context_) {}
 
 HealthCheckClient::CallState::~CallState() {
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO, "HealthCheckClient %p: destroying CallState %p",
             health_check_client_.get(), this);
   }
-  // The subchannel call is in the arena, so reset the pointer before we destroy
-  // the arena.
-  call_.reset();
   for (size_t i = 0; i < GRPC_CONTEXT_COUNT; i++) {
     if (context_[i].destroy != nullptr) {
       context_[i].destroy(context_[i].value);
@@ -309,14 +299,13 @@
   // holding to the call stack. Also flush the closures on exec_ctx so that
   // filters that schedule cancel notification closures on exec_ctx do not
   // need to take a ref of the call stack to guarantee closure liveness.
-  grpc_call_combiner_set_notify_on_cancel(&call_combiner_, nullptr);
-  grpc_core::ExecCtx::Get()->Flush();
-  grpc_call_combiner_destroy(&call_combiner_);
-  gpr_arena_destroy(arena_);
+  call_combiner_.SetNotifyOnCancel(nullptr);
+  ExecCtx::Get()->Flush();
+  arena_->Destroy();
 }
 
 void HealthCheckClient::CallState::Orphan() {
-  grpc_call_combiner_cancel(&call_combiner_, GRPC_ERROR_CANCELLED);
+  call_combiner_.Cancel(GRPC_ERROR_CANCELLED);
   Cancel();
 }
 
@@ -332,7 +321,8 @@
       0,  // parent_data_size
   };
   grpc_error* error = GRPC_ERROR_NONE;
-  call_ = health_check_client_->connected_subchannel_->CreateCall(args, &error);
+  call_ = health_check_client_->connected_subchannel_->CreateCall(args, &error)
+              .release();
   if (error != GRPC_ERROR_NONE) {
     gpr_log(GPR_ERROR,
             "HealthCheckClient %p CallState %p: error creating health "
@@ -341,18 +331,22 @@
     GRPC_ERROR_UNREF(error);
     // Schedule instead of running directly, since we must not be
     // holding health_check_client_->mu_ when CallEnded() is called.
-    Ref(DEBUG_LOCATION, "call_end_closure").release();
+    call_->Ref(DEBUG_LOCATION, "call_end_closure").release();
     GRPC_CLOSURE_SCHED(
         GRPC_CLOSURE_INIT(&batch_.handler_private.closure, CallEndedRetry, this,
                           grpc_schedule_on_exec_ctx),
         GRPC_ERROR_NONE);
     return;
   }
+  // Register after-destruction callback.
+  GRPC_CLOSURE_INIT(&after_call_stack_destruction_, AfterCallStackDestruction,
+                    this, grpc_schedule_on_exec_ctx);
+  call_->SetAfterCallStackDestroy(&after_call_stack_destruction_);
   // Initialize payload and batch.
   payload_.context = context_;
   batch_.payload = &payload_;
   // on_complete callback takes ref, handled manually.
-  Ref(DEBUG_LOCATION, "on_complete").release();
+  call_->Ref(DEBUG_LOCATION, "on_complete").release();
   batch_.on_complete = GRPC_CLOSURE_INIT(&on_complete_, OnComplete, this,
                                          grpc_schedule_on_exec_ctx);
   // Add send_initial_metadata op.
@@ -385,7 +379,7 @@
   payload_.recv_initial_metadata.trailing_metadata_available = nullptr;
   payload_.recv_initial_metadata.peer_string = nullptr;
   // recv_initial_metadata_ready callback takes ref, handled manually.
-  Ref(DEBUG_LOCATION, "recv_initial_metadata_ready").release();
+  call_->Ref(DEBUG_LOCATION, "recv_initial_metadata_ready").release();
   payload_.recv_initial_metadata.recv_initial_metadata_ready =
       GRPC_CLOSURE_INIT(&recv_initial_metadata_ready_, RecvInitialMetadataReady,
                         this, grpc_schedule_on_exec_ctx);
@@ -393,7 +387,7 @@
   // Add recv_message op.
   payload_.recv_message.recv_message = &recv_message_;
   // recv_message callback takes ref, handled manually.
-  Ref(DEBUG_LOCATION, "recv_message_ready").release();
+  call_->Ref(DEBUG_LOCATION, "recv_message_ready").release();
   payload_.recv_message.recv_message_ready = GRPC_CLOSURE_INIT(
       &recv_message_ready_, RecvMessageReady, this, grpc_schedule_on_exec_ctx);
   batch_.recv_message = true;
@@ -429,19 +423,26 @@
 
 void HealthCheckClient::CallState::StartBatch(
     grpc_transport_stream_op_batch* batch) {
-  batch->handler_private.extra_arg = call_.get();
+  batch->handler_private.extra_arg = call_;
   GRPC_CLOSURE_INIT(&batch->handler_private.closure, StartBatchInCallCombiner,
                     batch, grpc_schedule_on_exec_ctx);
   GRPC_CALL_COMBINER_START(&call_combiner_, &batch->handler_private.closure,
                            GRPC_ERROR_NONE, "start_subchannel_batch");
 }
 
+void HealthCheckClient::CallState::AfterCallStackDestruction(
+    void* arg, grpc_error* error) {
+  HealthCheckClient::CallState* self =
+      static_cast<HealthCheckClient::CallState*>(arg);
+  Delete(self);
+}
+
 void HealthCheckClient::CallState::OnCancelComplete(void* arg,
                                                     grpc_error* error) {
   HealthCheckClient::CallState* self =
       static_cast<HealthCheckClient::CallState*>(arg);
   GRPC_CALL_COMBINER_STOP(&self->call_combiner_, "health_cancel");
-  self->Unref(DEBUG_LOCATION, "cancel");
+  self->call_->Unref(DEBUG_LOCATION, "cancel");
 }
 
 void HealthCheckClient::CallState::StartCancel(void* arg, grpc_error* error) {
@@ -455,8 +456,10 @@
 }
 
 void HealthCheckClient::CallState::Cancel() {
-  if (call_ != nullptr) {
-    Ref(DEBUG_LOCATION, "cancel").release();
+  bool expected = false;
+  if (cancelled_.CompareExchangeStrong(&expected, true, MemoryOrder::ACQ_REL,
+                                       MemoryOrder::ACQUIRE)) {
+    call_->Ref(DEBUG_LOCATION, "cancel").release();
     GRPC_CALL_COMBINER_START(
         &call_combiner_,
         GRPC_CLOSURE_CREATE(StartCancel, this, grpc_schedule_on_exec_ctx),
@@ -470,7 +473,7 @@
   GRPC_CALL_COMBINER_STOP(&self->call_combiner_, "on_complete");
   grpc_metadata_batch_destroy(&self->send_initial_metadata_);
   grpc_metadata_batch_destroy(&self->send_trailing_metadata_);
-  self->Unref(DEBUG_LOCATION, "on_complete");
+  self->call_->Unref(DEBUG_LOCATION, "on_complete");
 }
 
 void HealthCheckClient::CallState::RecvInitialMetadataReady(void* arg,
@@ -479,7 +482,7 @@
       static_cast<HealthCheckClient::CallState*>(arg);
   GRPC_CALL_COMBINER_STOP(&self->call_combiner_, "recv_initial_metadata_ready");
   grpc_metadata_batch_destroy(&self->recv_initial_metadata_);
-  self->Unref(DEBUG_LOCATION, "recv_initial_metadata_ready");
+  self->call_->Unref(DEBUG_LOCATION, "recv_initial_metadata_ready");
 }
 
 void HealthCheckClient::CallState::DoneReadingRecvMessage(grpc_error* error) {
@@ -488,7 +491,7 @@
     GRPC_ERROR_UNREF(error);
     Cancel();
     grpc_slice_buffer_destroy_internal(&recv_message_buffer_);
-    Unref(DEBUG_LOCATION, "recv_message_ready");
+    call_->Unref(DEBUG_LOCATION, "recv_message_ready");
     return;
   }
   const bool healthy = DecodeResponse(&recv_message_buffer_, &error);
@@ -498,7 +501,7 @@
     error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("backend unhealthy");
   }
   health_check_client_->SetHealthStatus(state, error);
-  gpr_atm_rel_store(&seen_response_, static_cast<gpr_atm>(1));
+  seen_response_.Store(true, MemoryOrder::RELEASE);
   grpc_slice_buffer_destroy_internal(&recv_message_buffer_);
   // Start another recv_message batch.
   // This re-uses the ref we're holding.
@@ -561,7 +564,7 @@
       static_cast<HealthCheckClient::CallState*>(arg);
   GRPC_CALL_COMBINER_STOP(&self->call_combiner_, "recv_message_ready");
   if (self->recv_message_ == nullptr) {
-    self->Unref(DEBUG_LOCATION, "recv_message_ready");
+    self->call_->Unref(DEBUG_LOCATION, "recv_message_ready");
     return;
   }
   grpc_slice_buffer_init(&self->recv_message_buffer_);
@@ -587,7 +590,7 @@
     status = grpc_get_status_code_from_metadata(
         self->recv_trailing_metadata_.idx.named.grpc_status->md);
   }
-  if (grpc_health_check_client_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_health_check_client_trace)) {
     gpr_log(GPR_INFO,
             "HealthCheckClient %p CallState %p: health watch failed with "
             "status %d",
@@ -619,7 +622,7 @@
   HealthCheckClient::CallState* self =
       static_cast<HealthCheckClient::CallState*>(arg);
   self->CallEnded(true /* retry */);
-  self->Unref(DEBUG_LOCATION, "call_end_closure");
+  self->call_->Unref(DEBUG_LOCATION, "call_end_closure");
 }
 
 void HealthCheckClient::CallState::CallEnded(bool retry) {
@@ -631,7 +634,7 @@
     health_check_client_->call_state_.reset();
     if (retry) {
       GPR_ASSERT(!health_check_client_->shutting_down_);
-      if (static_cast<bool>(gpr_atm_acq_load(&seen_response_))) {
+      if (seen_response_.Load(MemoryOrder::ACQUIRE)) {
         // If the call fails after we've gotten a successful response, reset
         // the backoff and restart the call immediately.
         health_check_client_->retry_backoff_.Reset();
@@ -642,7 +645,9 @@
       }
     }
   }
-  Unref(DEBUG_LOCATION, "call_ended");
+  // When the last ref to the call stack goes away, the CallState object
+  // will be automatically destroyed.
+  call_->Unref(DEBUG_LOCATION, "call_ended");
 }
 
 }  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/health/health_check_client.h b/src/core/ext/filters/client_channel/health/health_check_client.h
index 7af88a5..956c109 100644
--- a/src/core/ext/filters/client_channel/health/health_check_client.h
+++ b/src/core/ext/filters/client_channel/health/health_check_client.h
@@ -22,15 +22,16 @@
 #include <grpc/support/port_platform.h>
 
 #include <grpc/grpc.h>
-#include <grpc/support/atm.h>
 #include <grpc/support/sync.h>
 
 #include "src/core/ext/filters/client_channel/client_channel_channelz.h"
 #include "src/core/ext/filters/client_channel/subchannel.h"
 #include "src/core/lib/backoff/backoff.h"
-#include "src/core/lib/gpr/arena.h"
+#include "src/core/lib/gprpp/arena.h"
+#include "src/core/lib/gprpp/atomic.h"
 #include "src/core/lib/gprpp/orphanable.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/call_combiner.h"
 #include "src/core/lib/iomgr/closure.h"
 #include "src/core/lib/iomgr/polling_entity.h"
@@ -60,7 +61,7 @@
 
  private:
   // Contains a call to the backend and all the data related to the call.
-  class CallState : public InternallyRefCounted<CallState> {
+  class CallState : public Orphanable {
    public:
     CallState(RefCountedPtr<HealthCheckClient> health_check_client,
               grpc_pollset_set* interested_parties_);
@@ -91,15 +92,19 @@
     grpc_error* PullSliceFromRecvMessage();
     void DoneReadingRecvMessage(grpc_error* error);
 
+    static void AfterCallStackDestruction(void* arg, grpc_error* error);
+
     RefCountedPtr<HealthCheckClient> health_check_client_;
     grpc_polling_entity pollent_;
 
-    gpr_arena* arena_;
-    grpc_call_combiner call_combiner_;
+    Arena* arena_;
+    grpc_core::CallCombiner call_combiner_;
     grpc_call_context_element context_[GRPC_CONTEXT_COUNT] = {};
 
-    // The streaming call to the backend. Always non-NULL.
-    RefCountedPtr<SubchannelCall> call_;
+    // The streaming call to the backend. Always non-null.
+    // Refs are tracked manually; when the last ref is released, the
+    // CallState object will be automatically destroyed.
+    SubchannelCall* call_;
 
     grpc_transport_stream_op_batch_payload payload_;
     grpc_transport_stream_op_batch batch_;
@@ -126,12 +131,18 @@
     OrphanablePtr<ByteStream> recv_message_;
     grpc_closure recv_message_ready_;
     grpc_slice_buffer recv_message_buffer_;
-    gpr_atm seen_response_;
+    Atomic<bool> seen_response_{false};
 
     // recv_trailing_metadata
     grpc_metadata_batch recv_trailing_metadata_;
     grpc_transport_stream_stats collect_stats_;
     grpc_closure recv_trailing_metadata_ready_;
+
+    // True if the cancel_stream batch has been started.
+    Atomic<bool> cancelled_{false};
+
+    // Closure for call stack destruction.
+    grpc_closure after_call_stack_destruction_;
   };
 
   void StartCall();
@@ -149,7 +160,7 @@
   grpc_pollset_set* interested_parties_;  // Do not own.
   RefCountedPtr<channelz::SubchannelNode> channelz_node_;
 
-  gpr_mu mu_;
+  Mutex mu_;
   grpc_connectivity_state state_ = GRPC_CHANNEL_CONNECTING;
   grpc_error* error_ = GRPC_ERROR_NONE;
   grpc_connectivity_state* notify_state_ = nullptr;
diff --git a/src/core/ext/filters/client_channel/http_connect_handshaker.cc b/src/core/ext/filters/client_channel/http_connect_handshaker.cc
index 2b1eb92..95366b5 100644
--- a/src/core/ext/filters/client_channel/http_connect_handshaker.cc
+++ b/src/core/ext/filters/client_channel/http_connect_handshaker.cc
@@ -31,9 +31,8 @@
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/handshaker_registry.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/http/format_request.h"
 #include "src/core/lib/http/parser.h"
 #include "src/core/lib/slice/slice_internal.h"
diff --git a/src/core/ext/filters/client_channel/http_connect_handshaker.h b/src/core/ext/filters/client_channel/http_connect_handshaker.h
index 928a23d..26c31f2 100644
--- a/src/core/ext/filters/client_channel/http_connect_handshaker.h
+++ b/src/core/ext/filters/client_channel/http_connect_handshaker.h
@@ -25,7 +25,7 @@
 
 /// Channel arg indicating HTTP CONNECT headers (string).
 /// Multiple headers are separated by newlines.  Key/value pairs are
-/// seperated by colons.
+/// separated by colons.
 #define GRPC_ARG_HTTP_CONNECT_HEADERS "grpc.http_connect_headers"
 
 /// Registers handshaker factory.
diff --git a/src/core/ext/filters/client_channel/lb_policy.cc b/src/core/ext/filters/client_channel/lb_policy.cc
index c8f8e82..6fa7993 100644
--- a/src/core/ext/filters/client_channel/lb_policy.cc
+++ b/src/core/ext/filters/client_channel/lb_policy.cc
@@ -62,32 +62,6 @@
   policy->Unref();
 }
 
-grpc_json* LoadBalancingPolicy::ParseLoadBalancingConfig(
-    const grpc_json* lb_config_array) {
-  if (lb_config_array == nullptr || lb_config_array->type != GRPC_JSON_ARRAY) {
-    return nullptr;
-  }
-  // Find the first LB policy that this client supports.
-  for (const grpc_json* lb_config = lb_config_array->child;
-       lb_config != nullptr; lb_config = lb_config->next) {
-    if (lb_config->type != GRPC_JSON_OBJECT) return nullptr;
-    grpc_json* policy = nullptr;
-    for (grpc_json* field = lb_config->child; field != nullptr;
-         field = field->next) {
-      if (field->key == nullptr || field->type != GRPC_JSON_OBJECT)
-        return nullptr;
-      if (policy != nullptr) return nullptr;  // Violate "oneof" type.
-      policy = field;
-    }
-    if (policy == nullptr) return nullptr;
-    // If we support this policy, then select it.
-    if (LoadBalancingPolicyRegistry::LoadBalancingPolicyExists(policy->key)) {
-      return policy;
-    }
-  }
-  return nullptr;
-}
-
 //
 // LoadBalancingPolicy::UpdateArgs
 //
@@ -140,10 +114,9 @@
   //    the time this function returns, the pick will already have
   //    been processed, and we'll be trying to re-process the same
   //    pick again, leading to a crash.
-  // 2. In a subsequent PR, we will split the data plane and control
-  //    plane synchronization into separate combiners, at which
-  //    point this will need to hop from the data plane combiner into
-  //    the control plane combiner.
+  // 2. We are currently running in the data plane combiner, but we
+  //    need to bounce into the control plane combiner to call
+  //    ExitIdleLocked().
   if (!exit_idle_called_) {
     exit_idle_called_ = true;
     parent_->Ref().release();  // ref held by closure.
diff --git a/src/core/ext/filters/client_channel/lb_policy.h b/src/core/ext/filters/client_channel/lb_policy.h
index 1c17f95..2ac7df6 100644
--- a/src/core/ext/filters/client_channel/lb_policy.h
+++ b/src/core/ext/filters/client_channel/lb_policy.h
@@ -36,6 +36,18 @@
 
 namespace grpc_core {
 
+/// Interface for parsed forms of load balancing configs found in a service
+/// config.
+class ParsedLoadBalancingConfig : public RefCounted<ParsedLoadBalancingConfig> {
+ public:
+  virtual ~ParsedLoadBalancingConfig() = default;
+
+  // Returns the load balancing policy name
+  virtual const char* name() const GRPC_ABSTRACT;
+
+  GRPC_ABSTRACT_BASE_CLASS;
+};
+
 /// Interface for load balancing policies.
 ///
 /// The following concepts are used here:
@@ -167,6 +179,9 @@
 
   /// A proxy object used by the LB policy to communicate with the client
   /// channel.
+  // TODO(juanlishen): Consider adding a mid-layer subclass that helps handle
+  // things like swapping in pending policy when it's ready. Currently, we are
+  // duplicating the logic in many subclasses.
   class ChannelControlHelper {
    public:
     ChannelControlHelper() = default;
@@ -185,7 +200,6 @@
     /// Sets the connectivity state and returns a new picker to be used
     /// by the client channel.
     virtual void UpdateState(grpc_connectivity_state state,
-                             grpc_error* state_error,
                              UniquePtr<SubchannelPicker>) GRPC_ABSTRACT;
 
     /// Requests that the resolver re-resolve.
@@ -194,30 +208,11 @@
     GRPC_ABSTRACT_BASE_CLASS
   };
 
-  /// Configuration for an LB policy instance.
-  // TODO(roth): Find a better JSON representation for this API.
-  class Config : public RefCounted<Config> {
-   public:
-    Config(const grpc_json* lb_config,
-           RefCountedPtr<ServiceConfig> service_config)
-        : json_(lb_config), service_config_(std::move(service_config)) {}
-
-    const char* name() const { return json_->key; }
-    const grpc_json* config() const { return json_->child; }
-    RefCountedPtr<ServiceConfig> service_config() const {
-      return service_config_;
-    }
-
-   private:
-    const grpc_json* json_;
-    RefCountedPtr<ServiceConfig> service_config_;
-  };
-
   /// Data passed to the UpdateLocked() method when new addresses and
   /// config are available.
   struct UpdateArgs {
     ServerAddressList addresses;
-    RefCountedPtr<Config> config;
+    RefCountedPtr<ParsedLoadBalancingConfig> config;
     const grpc_channel_args* args = nullptr;
 
     // TODO(roth): Remove everything below once channel args is
@@ -288,10 +283,6 @@
 
   void Orphan() override;
 
-  /// Returns the JSON node of policy (with both policy name and config content)
-  /// given the JSON node of a LoadBalancingConfig array.
-  static grpc_json* ParseLoadBalancingConfig(const grpc_json* lb_config_array);
-
   // A picker that returns PICK_QUEUE for all picks.
   // Also calls the parent LB policy's ExitIdleLocked() method when the
   // first pick is seen.
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
index 02fe06c..ed6e8de 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
@@ -88,7 +88,6 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
 #include "src/core/lib/gprpp/memory.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
 #include "src/core/lib/gprpp/orphanable.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/combiner.h"
@@ -119,6 +118,21 @@
 
 constexpr char kGrpclb[] = "grpclb";
 
+class ParsedGrpcLbConfig : public ParsedLoadBalancingConfig {
+ public:
+  explicit ParsedGrpcLbConfig(
+      RefCountedPtr<ParsedLoadBalancingConfig> child_policy)
+      : child_policy_(std::move(child_policy)) {}
+  const char* name() const override { return kGrpclb; }
+
+  RefCountedPtr<ParsedLoadBalancingConfig> child_policy() const {
+    return child_policy_;
+  }
+
+ private:
+  RefCountedPtr<ParsedLoadBalancingConfig> child_policy_;
+};
+
 class GrpcLb : public LoadBalancingPolicy {
  public:
   explicit GrpcLb(Args args);
@@ -234,12 +248,19 @@
 
     // Returns the LB token to use for a drop, or null if the call
     // should not be dropped.
-    // Intended to be called from picker, so calls will be externally
-    // synchronized.
+    //
+    // Note: This is called from the picker, so it will be invoked in
+    // the channel's data plane combiner, NOT the control plane
+    // combiner.  It should not be accessed by any other part of the LB
+    // policy.
     const char* ShouldDrop();
 
    private:
     grpc_grpclb_serverlist* serverlist_;
+
+    // Guarded by the channel's data plane combiner, NOT the control
+    // plane combiner.  It should not be accessed by anything but the
+    // picker via the ShouldDrop() method.
     size_t drop_index_ = 0;
   };
 
@@ -275,7 +296,7 @@
     Subchannel* CreateSubchannel(const grpc_channel_args& args) override;
     grpc_channel* CreateChannel(const char* target,
                                 const grpc_channel_args& args) override;
-    void UpdateState(grpc_connectivity_state state, grpc_error* state_error,
+    void UpdateState(grpc_connectivity_state state,
                      UniquePtr<SubchannelPicker> picker) override;
     void RequestReresolution() override;
 
@@ -296,7 +317,6 @@
   // Helper functions used in UpdateLocked().
   void ProcessAddressesAndChannelArgsLocked(const ServerAddressList& addresses,
                                             const grpc_channel_args& args);
-  void ParseLbConfig(Config* grpclb_config);
   static void OnBalancerChannelConnectivityChangedLocked(void* arg,
                                                          grpc_error* error);
   void CancelBalancerChannelConnectivityWatchLocked();
@@ -374,7 +394,7 @@
   // until it reports READY, at which point it will be moved to child_policy_.
   OrphanablePtr<LoadBalancingPolicy> pending_child_policy_;
   // The child policy config.
-  RefCountedPtr<Config> child_policy_config_;
+  RefCountedPtr<ParsedLoadBalancingConfig> child_policy_config_;
   // Child policy in state READY.
   bool child_policy_ready_ = false;
 };
@@ -551,7 +571,7 @@
     // subchannel call (and therefore no client_load_reporting filter)
     // for dropped calls.
     if (client_stats_ != nullptr) {
-      client_stats_->AddCallDroppedLocked(drop_token);
+      client_stats_->AddCallDropped(drop_token);
     }
     return PICK_COMPLETE;
   }
@@ -615,25 +635,18 @@
 }
 
 void GrpcLb::Helper::UpdateState(grpc_connectivity_state state,
-                                 grpc_error* state_error,
                                  UniquePtr<SubchannelPicker> picker) {
-  if (parent_->shutting_down_) {
-    GRPC_ERROR_UNREF(state_error);
-    return;
-  }
+  if (parent_->shutting_down_) return;
   // If this request is from the pending child policy, ignore it until
   // it reports READY, at which point we swap it into place.
   if (CalledByPendingChild()) {
-    if (grpc_lb_glb_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
       gpr_log(GPR_INFO,
               "[grpclb %p helper %p] pending child policy %p reports state=%s",
               parent_.get(), this, parent_->pending_child_policy_.get(),
               grpc_connectivity_state_name(state));
     }
-    if (state != GRPC_CHANNEL_READY) {
-      GRPC_ERROR_UNREF(state_error);
-      return;
-    }
+    if (state != GRPC_CHANNEL_READY) return;
     grpc_pollset_set_del_pollset_set(
         parent_->child_policy_->interested_parties(),
         parent_->interested_parties());
@@ -641,7 +654,6 @@
     parent_->child_policy_ = std::move(parent_->pending_child_policy_);
   } else if (!CalledByCurrentChild()) {
     // This request is from an outdated child, so ignore it.
-    GRPC_ERROR_UNREF(state_error);
     return;
   }
   // Record whether child policy reports READY.
@@ -670,18 +682,17 @@
   if (parent_->serverlist_ == nullptr ||
       (!parent_->serverlist_->ContainsAllDropEntries() &&
        state != GRPC_CHANNEL_READY)) {
-    if (grpc_lb_glb_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
       gpr_log(GPR_INFO,
               "[grpclb %p helper %p] state=%s passing child picker %p as-is",
               parent_.get(), this, grpc_connectivity_state_name(state),
               picker.get());
     }
-    parent_->channel_control_helper()->UpdateState(state, state_error,
-                                                   std::move(picker));
+    parent_->channel_control_helper()->UpdateState(state, std::move(picker));
     return;
   }
   // Cases 2 and 3a: wrap picker from the child in our own picker.
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO, "[grpclb %p helper %p] state=%s wrapping child picker %p",
             parent_.get(), this, grpc_connectivity_state_name(state),
             picker.get());
@@ -692,10 +703,9 @@
     client_stats = parent_->lb_calld_->client_stats()->Ref();
   }
   parent_->channel_control_helper()->UpdateState(
-      state, state_error,
-      UniquePtr<SubchannelPicker>(
-          New<Picker>(parent_.get(), parent_->serverlist_, std::move(picker),
-                      std::move(client_stats))));
+      state, UniquePtr<SubchannelPicker>(
+                 New<Picker>(parent_.get(), parent_->serverlist_,
+                             std::move(picker), std::move(client_stats))));
 }
 
 void GrpcLb::Helper::RequestReresolution() {
@@ -705,7 +715,7 @@
           ? parent_->pending_child_policy_.get()
           : parent_->child_policy_.get();
   if (child_ != latest_child_policy) return;
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO,
             "[grpclb %p] Re-resolution requested from %schild policy (%p).",
             parent_.get(), CalledByPendingChild() ? "pending " : "", child_);
@@ -792,7 +802,7 @@
 
 void GrpcLb::BalancerCallState::StartQuery() {
   GPR_ASSERT(lb_call_ != nullptr);
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO, "[grpclb %p] lb_calld=%p: Starting LB call %p",
             grpclb_policy_.get(), this, lb_call_);
   }
@@ -910,7 +920,7 @@
   // Construct message payload.
   GPR_ASSERT(send_message_payload_ == nullptr);
   grpc_grpclb_request* request =
-      grpc_grpclb_load_report_request_create_locked(client_stats_.get());
+      grpc_grpclb_load_report_request_create(client_stats_.get());
   // Skip client load report if the counters were all zero in the last
   // report and they are still zero in this one.
   if (LoadReportCountersAreZero(request)) {
@@ -999,7 +1009,7 @@
       lb_calld->client_stats_report_interval_ = GPR_MAX(
           GPR_MS_PER_SEC, grpc_grpclb_duration_to_millis(
                               &initial_response->client_stats_report_interval));
-      if (grpc_lb_glb_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
         gpr_log(GPR_INFO,
                 "[grpclb %p] lb_calld=%p: Received initial LB response "
                 "message; client load reporting interval = %" PRId64
@@ -1007,7 +1017,7 @@
                 grpclb_policy, lb_calld,
                 lb_calld->client_stats_report_interval_);
       }
-    } else if (grpc_lb_glb_trace.enabled()) {
+    } else if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
       gpr_log(GPR_INFO,
               "[grpclb %p] lb_calld=%p: Received initial LB response message; "
               "client load reporting NOT enabled",
@@ -1020,7 +1030,7 @@
     // Have seen initial response, look for serverlist.
     GPR_ASSERT(lb_calld->lb_call_ != nullptr);
     auto serverlist_wrapper = MakeRefCounted<Serverlist>(serverlist);
-    if (grpc_lb_glb_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
       UniquePtr<char> serverlist_text = serverlist_wrapper->AsText();
       gpr_log(GPR_INFO,
               "[grpclb %p] lb_calld=%p: Serverlist with %" PRIuPTR
@@ -1041,7 +1051,7 @@
     // Check if the serverlist differs from the previous one.
     if (grpclb_policy->serverlist_ != nullptr &&
         *grpclb_policy->serverlist_ == *serverlist_wrapper) {
-      if (grpc_lb_glb_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
         gpr_log(GPR_INFO,
                 "[grpclb %p] lb_calld=%p: Incoming server list identical to "
                 "current, ignoring.",
@@ -1119,7 +1129,7 @@
   BalancerCallState* lb_calld = static_cast<BalancerCallState*>(arg);
   GrpcLb* grpclb_policy = lb_calld->grpclb_policy();
   GPR_ASSERT(lb_calld->lb_call_ != nullptr);
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     char* status_details =
         grpc_slice_to_c_string(lb_calld->lb_call_status_details_);
     gpr_log(GPR_INFO,
@@ -1133,13 +1143,13 @@
   // we want to retry connecting. Otherwise, we have deliberately ended this
   // call and no further action is required.
   if (lb_calld == grpclb_policy->lb_calld_.get()) {
-    // If we did not receive a serverlist and the fallback-at-startup checks
-    // are pending, go into fallback mode immediately.  This short-circuits
-    // the timeout for the fallback-at-startup case.
-    if (!lb_calld->seen_serverlist_ &&
-        grpclb_policy->fallback_at_startup_checks_pending_) {
+    // If the fallback-at-startup checks are pending, go into fallback mode
+    // immediately.  This short-circuits the timeout for the fallback-at-startup
+    // case.
+    if (grpclb_policy->fallback_at_startup_checks_pending_) {
+      GPR_ASSERT(!lb_calld->seen_serverlist_);
       gpr_log(GPR_INFO,
-              "[grpclb %p] balancer call finished without receiving "
+              "[grpclb %p] Balancer call finished without receiving "
               "serverlist; entering fallback mode",
               grpclb_policy);
       grpclb_policy->fallback_at_startup_checks_pending_ = false;
@@ -1281,7 +1291,7 @@
   grpc_uri* uri = grpc_uri_parse(server_uri, true);
   GPR_ASSERT(uri->path[0] != '\0');
   server_name_ = gpr_strdup(uri->path[0] == '/' ? uri->path + 1 : uri->path);
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO,
             "[grpclb %p] Will use '%s' as the server name for LB request.",
             this, server_name_);
@@ -1377,7 +1387,13 @@
 
 void GrpcLb::UpdateLocked(UpdateArgs args) {
   const bool is_initial_update = lb_channel_ == nullptr;
-  ParseLbConfig(args.config.get());
+  auto* grpclb_config =
+      static_cast<const ParsedGrpcLbConfig*>(args.config.get());
+  if (grpclb_config != nullptr) {
+    child_policy_config_ = grpclb_config->child_policy();
+  } else {
+    child_policy_config_ = nullptr;
+  }
   ProcessAddressesAndChannelArgsLocked(args.addresses, *args.args);
   // Update the existing child policy.
   if (child_policy_ != nullptr) CreateOrUpdateChildPolicyLocked();
@@ -1466,27 +1482,6 @@
   response_generator_->SetResponse(std::move(result));
 }
 
-void GrpcLb::ParseLbConfig(Config* grpclb_config) {
-  const grpc_json* child_policy = nullptr;
-  if (grpclb_config != nullptr) {
-    const grpc_json* grpclb_config_json = grpclb_config->config();
-    for (const grpc_json* field = grpclb_config_json; field != nullptr;
-         field = field->next) {
-      if (field->key == nullptr) return;
-      if (strcmp(field->key, "childPolicy") == 0) {
-        if (child_policy != nullptr) return;  // Duplicate.
-        child_policy = ParseLoadBalancingConfig(field);
-      }
-    }
-  }
-  if (child_policy != nullptr) {
-    child_policy_config_ =
-        MakeRefCounted<Config>(child_policy, grpclb_config->service_config());
-  } else {
-    child_policy_config_.reset();
-  }
-}
-
 void GrpcLb::OnBalancerChannelConnectivityChangedLocked(void* arg,
                                                         grpc_error* error) {
   GrpcLb* self = static_cast<GrpcLb*>(arg);
@@ -1540,7 +1535,7 @@
   // Init the LB call data.
   GPR_ASSERT(lb_calld_ == nullptr);
   lb_calld_ = MakeOrphanable<BalancerCallState>(Ref());
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO,
             "[grpclb %p] Query for backends (lb_channel: %p, lb_calld: %p)",
             this, lb_channel_, lb_calld_.get());
@@ -1550,7 +1545,7 @@
 
 void GrpcLb::StartBalancerCallRetryTimerLocked() {
   grpc_millis next_try = lb_call_backoff_.NextAttemptTime();
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO, "[grpclb %p] Connection to LB server lost...", this);
     grpc_millis timeout = next_try - ExecCtx::Get()->Now();
     if (timeout > 0) {
@@ -1577,7 +1572,7 @@
   grpclb_policy->retry_timer_callback_pending_ = false;
   if (!grpclb_policy->shutting_down_ && error == GRPC_ERROR_NONE &&
       grpclb_policy->lb_calld_ == nullptr) {
-    if (grpc_lb_glb_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
       gpr_log(GPR_INFO, "[grpclb %p] Restarting call to LB server",
               grpclb_policy);
     }
@@ -1632,20 +1627,16 @@
 
 grpc_channel_args* GrpcLb::CreateChildPolicyArgsLocked(
     bool is_backend_from_grpclb_load_balancer) {
-  grpc_arg args_to_add[2] = {
-      // A channel arg indicating if the target is a backend inferred from a
-      // grpclb load balancer.
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(
-              GRPC_ARG_ADDRESS_IS_BACKEND_FROM_GRPCLB_LOAD_BALANCER),
-          is_backend_from_grpclb_load_balancer),
-  };
-  size_t num_args_to_add = 1;
+  InlinedVector<grpc_arg, 2> args_to_add;
+  args_to_add.emplace_back(grpc_channel_arg_integer_create(
+      const_cast<char*>(GRPC_ARG_ADDRESS_IS_BACKEND_FROM_GRPCLB_LOAD_BALANCER),
+      is_backend_from_grpclb_load_balancer));
   if (is_backend_from_grpclb_load_balancer) {
-    args_to_add[num_args_to_add++] = grpc_channel_arg_integer_create(
-        const_cast<char*>(GRPC_ARG_INHIBIT_HEALTH_CHECKING), 1);
+    args_to_add.emplace_back(grpc_channel_arg_integer_create(
+        const_cast<char*>(GRPC_ARG_INHIBIT_HEALTH_CHECKING), 1));
   }
-  return grpc_channel_args_copy_and_add(args_, args_to_add, num_args_to_add);
+  return grpc_channel_args_copy_and_add(args_, args_to_add.data(),
+                                        args_to_add.size());
 }
 
 OrphanablePtr<LoadBalancingPolicy> GrpcLb::CreateChildPolicyLocked(
@@ -1665,7 +1656,7 @@
     return nullptr;
   }
   helper->set_child(lb_policy.get());
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO, "[grpclb %p] Created new child policy %s (%p)", this,
             name, lb_policy.get());
   }
@@ -1764,7 +1755,7 @@
     // Cases 1, 2b, and 3b: create a new child policy.
     // If child_policy_ is null, we set it (case 1), else we set
     // pending_child_policy_ (cases 2b and 3b).
-    if (grpc_lb_glb_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
       gpr_log(GPR_INFO, "[grpclb %p] Creating new %schild policy %s", this,
               child_policy_ == nullptr ? "" : "pending ", child_policy_name);
     }
@@ -1788,7 +1779,7 @@
   }
   GPR_ASSERT(policy_to_update != nullptr);
   // Update the policy.
-  if (grpc_lb_glb_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_glb_trace)) {
     gpr_log(GPR_INFO, "[grpclb %p] Updating %schild policy %p", this,
             policy_to_update == pending_child_policy_.get() ? "pending " : "",
             policy_to_update);
@@ -1808,6 +1799,40 @@
   }
 
   const char* name() const override { return kGrpclb; }
+
+  RefCountedPtr<ParsedLoadBalancingConfig> ParseLoadBalancingConfig(
+      const grpc_json* json, grpc_error** error) const override {
+    GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+    if (json == nullptr) {
+      return RefCountedPtr<ParsedLoadBalancingConfig>(
+          New<ParsedGrpcLbConfig>(nullptr));
+    }
+    InlinedVector<grpc_error*, 2> error_list;
+    RefCountedPtr<ParsedLoadBalancingConfig> child_policy;
+    for (const grpc_json* field = json->child; field != nullptr;
+         field = field->next) {
+      if (field->key == nullptr) continue;
+      if (strcmp(field->key, "childPolicy") == 0) {
+        if (child_policy != nullptr) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:childPolicy error:Duplicate entry"));
+        }
+        grpc_error* parse_error = GRPC_ERROR_NONE;
+        child_policy = LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(
+            field, &parse_error);
+        if (parse_error != GRPC_ERROR_NONE) {
+          error_list.push_back(parse_error);
+        }
+      }
+    }
+    if (error_list.empty()) {
+      return RefCountedPtr<ParsedLoadBalancingConfig>(
+          New<ParsedGrpcLbConfig>(std::move(child_policy)));
+    } else {
+      *error = GRPC_ERROR_CREATE_FROM_VECTOR("GrpcLb Parser", &error_list);
+      return nullptr;
+    }
+  }
 };
 
 }  // namespace
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc
index 1c7ed87..3512363 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc
@@ -25,6 +25,8 @@
 #include <grpc/support/atm.h>
 #include <grpc/support/string_util.h>
 
+#include "src/core/lib/gprpp/sync.h"
+
 namespace grpc_core {
 
 void GrpcLbClientStats::AddCallStarted() {
@@ -43,11 +45,12 @@
   }
 }
 
-void GrpcLbClientStats::AddCallDroppedLocked(const char* token) {
+void GrpcLbClientStats::AddCallDropped(const char* token) {
   // Increment num_calls_started and num_calls_finished.
   gpr_atm_full_fetch_add(&num_calls_started_, (gpr_atm)1);
   gpr_atm_full_fetch_add(&num_calls_finished_, (gpr_atm)1);
   // Record the drop.
+  MutexLock lock(&drop_count_mu_);
   if (drop_token_counts_ == nullptr) {
     drop_token_counts_.reset(New<DroppedCallCounts>());
   }
@@ -69,7 +72,7 @@
 
 }  // namespace
 
-void GrpcLbClientStats::GetLocked(
+void GrpcLbClientStats::Get(
     int64_t* num_calls_started, int64_t* num_calls_finished,
     int64_t* num_calls_finished_with_client_failed_to_send,
     int64_t* num_calls_finished_known_received,
@@ -80,6 +83,7 @@
                            &num_calls_finished_with_client_failed_to_send_);
   AtomicGetAndResetCounter(num_calls_finished_known_received,
                            &num_calls_finished_known_received_);
+  MutexLock lock(&drop_count_mu_);
   *drop_token_counts = std::move(drop_token_counts_);
 }
 
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h
index cb261ee..bcc6598 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h
@@ -26,6 +26,7 @@
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/gprpp/memory.h"
 #include "src/core/lib/gprpp/ref_counted.h"
+#include "src/core/lib/gprpp/sync.h"
 
 namespace grpc_core {
 
@@ -41,20 +42,16 @@
 
   typedef InlinedVector<DropTokenCount, 10> DroppedCallCounts;
 
-  GrpcLbClientStats() {}
-
   void AddCallStarted();
   void AddCallFinished(bool finished_with_client_failed_to_send,
                        bool finished_known_received);
 
-  // This method is not thread-safe; caller must synchronize.
-  void AddCallDroppedLocked(const char* token);
+  void AddCallDropped(const char* token);
 
-  // This method is not thread-safe; caller must synchronize.
-  void GetLocked(int64_t* num_calls_started, int64_t* num_calls_finished,
-                 int64_t* num_calls_finished_with_client_failed_to_send,
-                 int64_t* num_calls_finished_known_received,
-                 UniquePtr<DroppedCallCounts>* drop_token_counts);
+  void Get(int64_t* num_calls_started, int64_t* num_calls_finished,
+           int64_t* num_calls_finished_with_client_failed_to_send,
+           int64_t* num_calls_finished_known_received,
+           UniquePtr<DroppedCallCounts>* drop_token_counts);
 
   // A destruction function to use as the user_data key when attaching
   // client stats to a grpc_mdelem.
@@ -63,13 +60,12 @@
   }
 
  private:
-  // This field must only be accessed via *_locked() methods.
-  UniquePtr<DroppedCallCounts> drop_token_counts_;
-  // These fields may be accessed from multiple threads at a time.
   gpr_atm num_calls_started_ = 0;
   gpr_atm num_calls_finished_ = 0;
   gpr_atm num_calls_finished_with_client_failed_to_send_ = 0;
   gpr_atm num_calls_finished_known_received_ = 0;
+  Mutex drop_count_mu_;  // Guards drop_token_counts_.
+  UniquePtr<DroppedCallCounts> drop_token_counts_;
 };
 
 }  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc
index 594c8cf..b51db11 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc
@@ -107,7 +107,7 @@
   return true;
 }
 
-grpc_grpclb_request* grpc_grpclb_load_report_request_create_locked(
+grpc_grpclb_request* grpc_grpclb_load_report_request_create(
     grpc_core::GrpcLbClientStats* client_stats) {
   grpc_grpclb_request* req = static_cast<grpc_grpclb_request*>(
       gpr_zalloc(sizeof(grpc_grpclb_request)));
@@ -122,7 +122,7 @@
   req->client_stats.calls_finished_with_drop.funcs.encode = encode_drops;
   grpc_core::UniquePtr<grpc_core::GrpcLbClientStats::DroppedCallCounts>
       drop_counts;
-  client_stats->GetLocked(
+  client_stats->Get(
       &req->client_stats.num_calls_started,
       &req->client_stats.num_calls_finished,
       &req->client_stats.num_calls_finished_with_client_failed_to_send,
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h b/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h
index 3c1d41a..8005f6f 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h
@@ -43,7 +43,7 @@
 
 /** Create a request for a gRPC LB service under \a lb_service_name */
 grpc_grpclb_request* grpc_grpclb_request_create(const char* lb_service_name);
-grpc_grpclb_request* grpc_grpclb_load_report_request_create_locked(
+grpc_grpclb_request* grpc_grpclb_load_report_request_create(
     grpc_core::GrpcLbClientStats* client_stats);
 
 /** Protocol Buffers v3-encode \a request */
diff --git a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
index 3da0b59..bc2f6e5 100644
--- a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc
@@ -27,7 +27,7 @@
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/ext/filters/client_channel/subchannel.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
 #include "src/core/lib/transport/connectivity_state.h"
@@ -73,7 +73,7 @@
         : SubchannelData(subchannel_list, address, subchannel, combiner) {}
 
     void ProcessConnectivityChangeLocked(
-        grpc_connectivity_state connectivity_state, grpc_error* error) override;
+        grpc_connectivity_state connectivity_state) override;
 
     // Processes the connectivity change to READY for an unselected subchannel.
     void ProcessUnselectedReadyLocked();
@@ -154,30 +154,28 @@
 
   /// Lock and data used to capture snapshots of this channels child
   /// channels and subchannels. This data is consumed by channelz.
-  gpr_mu child_refs_mu_;
+  Mutex child_refs_mu_;
   channelz::ChildRefsList child_subchannels_;
   channelz::ChildRefsList child_channels_;
 };
 
 PickFirst::PickFirst(Args args) : LoadBalancingPolicy(std::move(args)) {
-  gpr_mu_init(&child_refs_mu_);
-  if (grpc_lb_pick_first_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
     gpr_log(GPR_INFO, "Pick First %p created.", this);
   }
 }
 
 PickFirst::~PickFirst() {
-  if (grpc_lb_pick_first_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
     gpr_log(GPR_INFO, "Destroying Pick First %p", this);
   }
-  gpr_mu_destroy(&child_refs_mu_);
   GPR_ASSERT(subchannel_list_ == nullptr);
   GPR_ASSERT(latest_pending_subchannel_list_ == nullptr);
 }
 
 void PickFirst::ShutdownLocked() {
   AutoChildRefsUpdater guard(this);
-  if (grpc_lb_pick_first_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
     gpr_log(GPR_INFO, "Pick First %p Shutting down", this);
   }
   shutdown_ = true;
@@ -186,14 +184,16 @@
 }
 
 void PickFirst::ExitIdleLocked() {
+  if (shutdown_) return;
   if (idle_) {
     idle_ = false;
     if (subchannel_list_ == nullptr ||
         subchannel_list_->num_subchannels() == 0) {
-      grpc_error* error =
-          GRPC_ERROR_CREATE_FROM_STATIC_STRING("No addresses to connect to");
+      grpc_error* error = grpc_error_set_int(
+          GRPC_ERROR_CREATE_FROM_STATIC_STRING("No addresses to connect to"),
+          GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
       channel_control_helper()->UpdateState(
-          GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(error),
+          GRPC_CHANNEL_TRANSIENT_FAILURE,
           UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(error)));
     } else {
       subchannel_list_->subchannel(0)
@@ -245,7 +245,7 @@
 
 void PickFirst::UpdateLocked(UpdateArgs args) {
   AutoChildRefsUpdater guard(this);
-  if (grpc_lb_pick_first_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
     gpr_log(GPR_INFO,
             "Pick First %p received update with %" PRIuPTR " addresses", this,
             args.addresses.size());
@@ -267,9 +267,11 @@
     // haven't gotten a non-empty update by the time the application tries
     // to start a new call.)
     if (!idle_) {
-      grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update");
+      grpc_error* error = grpc_error_set_int(
+          GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"),
+          GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
       channel_control_helper()->UpdateState(
-          GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(error),
+          GRPC_CHANNEL_TRANSIENT_FAILURE,
           UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(error)));
     }
     return;
@@ -283,9 +285,7 @@
   // check and instead do it in ExitIdleLocked().
   for (size_t i = 0; i < subchannel_list->num_subchannels(); ++i) {
     PickFirstSubchannelData* sd = subchannel_list->subchannel(i);
-    grpc_error* error = GRPC_ERROR_NONE;
-    grpc_connectivity_state state = sd->CheckConnectivityStateLocked(&error);
-    GRPC_ERROR_UNREF(error);
+    grpc_connectivity_state state = sd->CheckConnectivityStateLocked();
     if (state == GRPC_CHANNEL_READY) {
       subchannel_list_ = std::move(subchannel_list);
       sd->StartConnectivityWatchLocked();
@@ -317,7 +317,7 @@
     // We do have a selected subchannel (which means it's READY), so keep
     // using it until one of the subchannels in the new list reports READY.
     if (latest_pending_subchannel_list_ != nullptr) {
-      if (grpc_lb_pick_first_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
         gpr_log(GPR_INFO,
                 "Pick First %p Shutting down latest pending subchannel list "
                 "%p, about to be replaced by newer latest %p",
@@ -339,7 +339,7 @@
 }
 
 void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked(
-    grpc_connectivity_state connectivity_state, grpc_error* error) {
+    grpc_connectivity_state connectivity_state) {
   PickFirst* p = static_cast<PickFirst*>(subchannel_list()->policy());
   AutoChildRefsUpdater guard(p);
   // The notification must be for a subchannel in either the current or
@@ -349,7 +349,7 @@
   GPR_ASSERT(connectivity_state != GRPC_CHANNEL_SHUTDOWN);
   // Handle updates for the currently selected subchannel.
   if (p->selected_ == this) {
-    if (grpc_lb_pick_first_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
       gpr_log(GPR_INFO,
               "Pick First %p selected subchannel connectivity changed to %s", p,
               grpc_connectivity_state_name(connectivity_state));
@@ -358,7 +358,7 @@
     // pending update, switch to the pending update.
     if (connectivity_state != GRPC_CHANNEL_READY &&
         p->latest_pending_subchannel_list_ != nullptr) {
-      if (grpc_lb_pick_first_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
         gpr_log(GPR_INFO,
                 "Pick First %p promoting pending subchannel list %p to "
                 "replace %p",
@@ -370,17 +370,16 @@
       p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_);
       // Set our state to that of the pending subchannel list.
       if (p->subchannel_list_->in_transient_failure()) {
-        grpc_error* new_error =
-            GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
-                "selected subchannel failed; switching to pending update",
-                &error, 1);
+        grpc_error* error = grpc_error_set_int(
+            GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                "selected subchannel failed; switching to pending update"),
+            GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
         p->channel_control_helper()->UpdateState(
-            GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(new_error),
-            UniquePtr<SubchannelPicker>(
-                New<TransientFailurePicker>(new_error)));
+            GRPC_CHANNEL_TRANSIENT_FAILURE,
+            UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(error)));
       } else {
         p->channel_control_helper()->UpdateState(
-            GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE,
+            GRPC_CHANNEL_CONNECTING,
             UniquePtr<SubchannelPicker>(New<QueuePicker>(p->Ref())));
       }
     } else {
@@ -394,7 +393,7 @@
         p->selected_ = nullptr;
         StopConnectivityWatchLocked();
         p->channel_control_helper()->UpdateState(
-            GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE,
+            GRPC_CHANNEL_IDLE,
             UniquePtr<SubchannelPicker>(New<QueuePicker>(p->Ref())));
       } else {
         // This is unlikely but can happen when a subchannel has been asked
@@ -402,19 +401,17 @@
         // some connectivity state notifications.
         if (connectivity_state == GRPC_CHANNEL_READY) {
           p->channel_control_helper()->UpdateState(
-              GRPC_CHANNEL_READY, GRPC_ERROR_NONE,
-              UniquePtr<SubchannelPicker>(
-                  New<Picker>(connected_subchannel()->Ref())));
+              GRPC_CHANNEL_READY, UniquePtr<SubchannelPicker>(New<Picker>(
+                                      connected_subchannel()->Ref())));
         } else {  // CONNECTING
           p->channel_control_helper()->UpdateState(
-              connectivity_state, GRPC_ERROR_REF(error),
+              connectivity_state,
               UniquePtr<SubchannelPicker>(New<QueuePicker>(p->Ref())));
         }
         // Renew notification.
         RenewConnectivityWatchLocked();
       }
     }
-    GRPC_ERROR_UNREF(error);
     return;
   }
   // If we get here, there are two possible cases:
@@ -451,13 +448,13 @@
         subchannel_list()->set_in_transient_failure(true);
         // Only report new state in case 1.
         if (subchannel_list() == p->subchannel_list_.get()) {
-          grpc_error* new_error =
-              GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
-                  "failed to connect to all addresses", &error, 1);
+          grpc_error* error = grpc_error_set_int(
+              GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                  "failed to connect to all addresses"),
+              GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
           p->channel_control_helper()->UpdateState(
-              GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(new_error),
-              UniquePtr<SubchannelPicker>(
-                  New<TransientFailurePicker>(new_error)));
+              GRPC_CHANNEL_TRANSIENT_FAILURE,
+              UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(error)));
         }
       }
       sd->CheckConnectivityStateAndStartWatchingLocked();
@@ -468,7 +465,7 @@
       // Only update connectivity state in case 1.
       if (subchannel_list() == p->subchannel_list_.get()) {
         p->channel_control_helper()->UpdateState(
-            GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE,
+            GRPC_CHANNEL_CONNECTING,
             UniquePtr<SubchannelPicker>(New<QueuePicker>(p->Ref())));
       }
       // Renew notification.
@@ -478,7 +475,6 @@
     case GRPC_CHANNEL_SHUTDOWN:
       GPR_UNREACHABLE_CODE(break);
   }
-  GRPC_ERROR_UNREF(error);
 }
 
 void PickFirst::PickFirstSubchannelData::ProcessUnselectedReadyLocked() {
@@ -496,7 +492,7 @@
              subchannel_list() == p->latest_pending_subchannel_list_.get());
   // Case 2.  Promote p->latest_pending_subchannel_list_ to p->subchannel_list_.
   if (subchannel_list() == p->latest_pending_subchannel_list_.get()) {
-    if (grpc_lb_pick_first_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
       gpr_log(GPR_INFO,
               "Pick First %p promoting pending subchannel list %p to "
               "replace %p",
@@ -508,9 +504,9 @@
   // Cases 1 and 2.
   p->selected_ = this;
   p->channel_control_helper()->UpdateState(
-      GRPC_CHANNEL_READY, GRPC_ERROR_NONE,
+      GRPC_CHANNEL_READY,
       UniquePtr<SubchannelPicker>(New<Picker>(connected_subchannel()->Ref())));
-  if (grpc_lb_pick_first_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_pick_first_trace)) {
     gpr_log(GPR_INFO, "Pick First %p selected subchannel %p", p, subchannel());
   }
 }
@@ -519,9 +515,7 @@
     CheckConnectivityStateAndStartWatchingLocked() {
   PickFirst* p = static_cast<PickFirst*>(subchannel_list()->policy());
   // Check current state.
-  grpc_error* error = GRPC_ERROR_NONE;
-  grpc_connectivity_state current_state = CheckConnectivityStateLocked(&error);
-  GRPC_ERROR_UNREF(error);
+  grpc_connectivity_state current_state = CheckConnectivityStateLocked();
   // Start watch.
   StartConnectivityWatchLocked();
   // If current state is READY, select the subchannel now, since we started
@@ -532,6 +526,11 @@
   }
 }
 
+class ParsedPickFirstConfig : public ParsedLoadBalancingConfig {
+ public:
+  const char* name() const override { return kPickFirst; }
+};
+
 //
 // factory
 //
@@ -544,6 +543,15 @@
   }
 
   const char* name() const override { return kPickFirst; }
+
+  RefCountedPtr<ParsedLoadBalancingConfig> ParseLoadBalancingConfig(
+      const grpc_json* json, grpc_error** error) const override {
+    if (json != nullptr) {
+      GPR_DEBUG_ASSERT(strcmp(json->key, name()) == 0);
+    }
+    return RefCountedPtr<ParsedLoadBalancingConfig>(
+        New<ParsedPickFirstConfig>());
+  }
 };
 
 }  // namespace
diff --git a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
index d3faaad..6d60391 100644
--- a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc
@@ -36,8 +36,8 @@
 #include "src/core/ext/filters/client_channel/subchannel.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/debug/trace.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
 #include "src/core/lib/transport/connectivity_state.h"
@@ -92,11 +92,11 @@
     }
 
     void UpdateConnectivityStateLocked(
-        grpc_connectivity_state connectivity_state, grpc_error* error);
+        grpc_connectivity_state connectivity_state);
 
    private:
     void ProcessConnectivityChangeLocked(
-        grpc_connectivity_state connectivity_state, grpc_error* error) override;
+        grpc_connectivity_state connectivity_state) override;
 
     grpc_connectivity_state last_connectivity_state_ = GRPC_CHANNEL_IDLE;
   };
@@ -119,7 +119,6 @@
     }
 
     ~RoundRobinSubchannelList() {
-      GRPC_ERROR_UNREF(last_transient_failure_error_);
       RoundRobin* p = static_cast<RoundRobin*>(policy());
       p->Unref(DEBUG_LOCATION, "subchannel_list");
     }
@@ -129,11 +128,8 @@
 
     // Updates the counters of subchannels in each state when a
     // subchannel transitions from old_state to new_state.
-    // transient_failure_error is the error that is reported when
-    // new_state is TRANSIENT_FAILURE.
     void UpdateStateCountersLocked(grpc_connectivity_state old_state,
-                                   grpc_connectivity_state new_state,
-                                   grpc_error* transient_failure_error);
+                                   grpc_connectivity_state new_state);
 
     // If this subchannel list is the RR policy's current subchannel
     // list, updates the RR policy's connectivity state based on the
@@ -148,7 +144,6 @@
     size_t num_ready_ = 0;
     size_t num_connecting_ = 0;
     size_t num_transient_failure_ = 0;
-    grpc_error* last_transient_failure_error_ = GRPC_ERROR_NONE;
   };
 
   class Picker : public SubchannelPicker {
@@ -193,7 +188,7 @@
   bool shutdown_ = false;
   /// Lock and data used to capture snapshots of this channel's child
   /// channels and subchannels. This data is consumed by channelz.
-  gpr_mu child_refs_mu_;
+  Mutex child_refs_mu_;
   channelz::ChildRefsList child_subchannels_;
   channelz::ChildRefsList child_channels_;
 };
@@ -217,7 +212,7 @@
   // TODO(roth): rand(3) is not thread-safe.  This should be replaced with
   // something better as part of https://github.com/grpc/grpc/issues/17891.
   last_picked_index_ = rand() % subchannels_.size();
-  if (grpc_lb_round_robin_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_INFO,
             "[RR %p picker %p] created picker from subchannel_list=%p "
             "with %" PRIuPTR " READY subchannels; last_picked_index_=%" PRIuPTR,
@@ -229,7 +224,7 @@
 RoundRobin::PickResult RoundRobin::Picker::Pick(PickArgs* pick,
                                                 grpc_error** error) {
   last_picked_index_ = (last_picked_index_ + 1) % subchannels_.size();
-  if (grpc_lb_round_robin_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_INFO,
             "[RR %p picker %p] returning index %" PRIuPTR
             ", connected_subchannel=%p",
@@ -245,24 +240,22 @@
 //
 
 RoundRobin::RoundRobin(Args args) : LoadBalancingPolicy(std::move(args)) {
-  gpr_mu_init(&child_refs_mu_);
-  if (grpc_lb_round_robin_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_INFO, "[RR %p] Created", this);
   }
 }
 
 RoundRobin::~RoundRobin() {
-  if (grpc_lb_round_robin_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_INFO, "[RR %p] Destroying Round Robin policy", this);
   }
-  gpr_mu_destroy(&child_refs_mu_);
   GPR_ASSERT(subchannel_list_ == nullptr);
   GPR_ASSERT(latest_pending_subchannel_list_ == nullptr);
 }
 
 void RoundRobin::ShutdownLocked() {
   AutoChildRefsUpdater guard(this);
-  if (grpc_lb_round_robin_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_INFO, "[RR %p] Shutting down", this);
   }
   shutdown_ = true;
@@ -317,11 +310,10 @@
   // subchannel already used by some other channel may have a non-IDLE
   // state.
   for (size_t i = 0; i < num_subchannels(); ++i) {
-    grpc_error* error = GRPC_ERROR_NONE;
     grpc_connectivity_state state =
-        subchannel(i)->CheckConnectivityStateLocked(&error);
+        subchannel(i)->CheckConnectivityStateLocked();
     if (state != GRPC_CHANNEL_IDLE) {
-      subchannel(i)->UpdateConnectivityStateLocked(state, error);
+      subchannel(i)->UpdateConnectivityStateLocked(state);
     }
   }
   // Start connectivity watch for each subchannel.
@@ -335,8 +327,7 @@
 }
 
 void RoundRobin::RoundRobinSubchannelList::UpdateStateCountersLocked(
-    grpc_connectivity_state old_state, grpc_connectivity_state new_state,
-    grpc_error* transient_failure_error) {
+    grpc_connectivity_state old_state, grpc_connectivity_state new_state) {
   GPR_ASSERT(old_state != GRPC_CHANNEL_SHUTDOWN);
   GPR_ASSERT(new_state != GRPC_CHANNEL_SHUTDOWN);
   if (old_state == GRPC_CHANNEL_READY) {
@@ -356,8 +347,6 @@
   } else if (new_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
     ++num_transient_failure_;
   }
-  GRPC_ERROR_UNREF(last_transient_failure_error_);
-  last_transient_failure_error_ = transient_failure_error;
 }
 
 // Sets the RR policy's connectivity state and generates a new picker based
@@ -384,20 +373,21 @@
   if (num_ready_ > 0) {
     /* 1) READY */
     p->channel_control_helper()->UpdateState(
-        GRPC_CHANNEL_READY, GRPC_ERROR_NONE,
-        UniquePtr<SubchannelPicker>(New<Picker>(p, this)));
+        GRPC_CHANNEL_READY, UniquePtr<SubchannelPicker>(New<Picker>(p, this)));
   } else if (num_connecting_ > 0) {
     /* 2) CONNECTING */
     p->channel_control_helper()->UpdateState(
-        GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE,
+        GRPC_CHANNEL_CONNECTING,
         UniquePtr<SubchannelPicker>(New<QueuePicker>(p->Ref())));
   } else if (num_transient_failure_ == num_subchannels()) {
     /* 3) TRANSIENT_FAILURE */
+    grpc_error* error =
+        grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                               "connections to all backends failing"),
+                           GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
     p->channel_control_helper()->UpdateState(
         GRPC_CHANNEL_TRANSIENT_FAILURE,
-        GRPC_ERROR_REF(last_transient_failure_error_),
-        UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(
-            GRPC_ERROR_REF(last_transient_failure_error_))));
+        UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(error)));
   }
 }
 
@@ -413,7 +403,7 @@
       // therefore we would not be receiving a notification for them.
       GPR_ASSERT(p->latest_pending_subchannel_list_.get() == this);
       GPR_ASSERT(!shutting_down());
-      if (grpc_lb_round_robin_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
         const size_t old_num_subchannels =
             p->subchannel_list_ != nullptr
                 ? p->subchannel_list_->num_subchannels()
@@ -432,9 +422,9 @@
 }
 
 void RoundRobin::RoundRobinSubchannelData::UpdateConnectivityStateLocked(
-    grpc_connectivity_state connectivity_state, grpc_error* error) {
+    grpc_connectivity_state connectivity_state) {
   RoundRobin* p = static_cast<RoundRobin*>(subchannel_list()->policy());
-  if (grpc_lb_round_robin_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
     gpr_log(
         GPR_INFO,
         "[RR %p] connectivity changed for subchannel %p, subchannel_list %p "
@@ -445,12 +435,12 @@
         grpc_connectivity_state_name(connectivity_state));
   }
   subchannel_list()->UpdateStateCountersLocked(last_connectivity_state_,
-                                               connectivity_state, error);
+                                               connectivity_state);
   last_connectivity_state_ = connectivity_state;
 }
 
 void RoundRobin::RoundRobinSubchannelData::ProcessConnectivityChangeLocked(
-    grpc_connectivity_state connectivity_state, grpc_error* error) {
+    grpc_connectivity_state connectivity_state) {
   RoundRobin* p = static_cast<RoundRobin*>(subchannel_list()->policy());
   GPR_ASSERT(subchannel() != nullptr);
   // If the new state is TRANSIENT_FAILURE, re-resolve.
@@ -459,7 +449,7 @@
   // when the subchannel list was created, we'd wind up in a constant
   // loop of re-resolution.
   if (connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
-    if (grpc_lb_round_robin_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
       gpr_log(GPR_INFO,
               "[RR %p] Subchannel %p has gone into TRANSIENT_FAILURE. "
               "Requesting re-resolution",
@@ -470,20 +460,20 @@
   // Renew connectivity watch.
   RenewConnectivityWatchLocked();
   // Update state counters.
-  UpdateConnectivityStateLocked(connectivity_state, error);
+  UpdateConnectivityStateLocked(connectivity_state);
   // Update overall state and renew notification.
   subchannel_list()->UpdateRoundRobinStateFromSubchannelStateCountsLocked();
 }
 
 void RoundRobin::UpdateLocked(UpdateArgs args) {
   AutoChildRefsUpdater guard(this);
-  if (grpc_lb_round_robin_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_INFO, "[RR %p] received update with %" PRIuPTR " addresses",
             this, args.addresses.size());
   }
   // Replace latest_pending_subchannel_list_.
   if (latest_pending_subchannel_list_ != nullptr) {
-    if (grpc_lb_round_robin_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_round_robin_trace)) {
       gpr_log(GPR_INFO,
               "[RR %p] Shutting down previous pending subchannel list %p", this,
               latest_pending_subchannel_list_.get());
@@ -494,9 +484,11 @@
   if (latest_pending_subchannel_list_->num_subchannels() == 0) {
     // If the new list is empty, immediately promote the new list to the
     // current list and transition to TRANSIENT_FAILURE.
-    grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update");
+    grpc_error* error =
+        grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"),
+                           GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
     channel_control_helper()->UpdateState(
-        GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(error),
+        GRPC_CHANNEL_TRANSIENT_FAILURE,
         UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(error)));
     subchannel_list_ = std::move(latest_pending_subchannel_list_);
   } else if (subchannel_list_ == nullptr) {
@@ -511,6 +503,11 @@
   }
 }
 
+class ParsedRoundRobinConfig : public ParsedLoadBalancingConfig {
+ public:
+  const char* name() const override { return kRoundRobin; }
+};
+
 //
 // factory
 //
@@ -523,6 +520,15 @@
   }
 
   const char* name() const override { return kRoundRobin; }
+
+  RefCountedPtr<ParsedLoadBalancingConfig> ParseLoadBalancingConfig(
+      const grpc_json* json, grpc_error** error) const override {
+    if (json != nullptr) {
+      GPR_DEBUG_ASSERT(strcmp(json->key, name()) == 0);
+    }
+    return RefCountedPtr<ParsedLoadBalancingConfig>(
+        New<ParsedRoundRobinConfig>());
+  }
 };
 
 }  // namespace
diff --git a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h b/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h
index 4fde90c..4c48045 100644
--- a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h
+++ b/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h
@@ -51,7 +51,7 @@
     : public SubchannelData<MySubchannelList, MySubchannelData> {
  public:
   void ProcessConnectivityChangeLocked(
-      grpc_connectivity_state connectivity_state, grpc_error* error) override {
+      grpc_connectivity_state connectivity_state) override {
     // ...code to handle connectivity changes...
   }
 };
@@ -101,10 +101,10 @@
   // pending (i.e., between calling StartConnectivityWatchLocked() or
   // RenewConnectivityWatchLocked() and the resulting invocation of
   // ProcessConnectivityChangeLocked()).
-  grpc_connectivity_state CheckConnectivityStateLocked(grpc_error** error) {
+  grpc_connectivity_state CheckConnectivityStateLocked() {
     GPR_ASSERT(!connectivity_notification_pending_);
     pending_connectivity_state_unsafe_ = subchannel()->CheckConnectivity(
-        error, subchannel_list_->inhibit_health_checking());
+        subchannel_list_->inhibit_health_checking());
     UpdateConnectedSubchannelLocked();
     return pending_connectivity_state_unsafe_;
   }
@@ -153,8 +153,7 @@
   // Implementations must invoke either RenewConnectivityWatchLocked() or
   // StopConnectivityWatchLocked() before returning.
   virtual void ProcessConnectivityChangeLocked(
-      grpc_connectivity_state connectivity_state,
-      grpc_error* error) GRPC_ABSTRACT;
+      grpc_connectivity_state connectivity_state) GRPC_ABSTRACT;
 
   // Unrefs the subchannel.
   void UnrefSubchannelLocked(const char* reason);
@@ -299,7 +298,7 @@
 void SubchannelData<SubchannelListType, SubchannelDataType>::
     UnrefSubchannelLocked(const char* reason) {
   if (subchannel_ != nullptr) {
-    if (subchannel_list_->tracer()->enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(*subchannel_list_->tracer())) {
       gpr_log(GPR_INFO,
               "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
               " (subchannel %p): unreffing subchannel",
@@ -324,7 +323,7 @@
 template <typename SubchannelListType, typename SubchannelDataType>
 void SubchannelData<SubchannelListType,
                     SubchannelDataType>::StartConnectivityWatchLocked() {
-  if (subchannel_list_->tracer()->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*subchannel_list_->tracer())) {
     gpr_log(GPR_INFO,
             "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
             " (subchannel %p): starting watch: requesting connectivity change "
@@ -346,7 +345,7 @@
 template <typename SubchannelListType, typename SubchannelDataType>
 void SubchannelData<SubchannelListType,
                     SubchannelDataType>::RenewConnectivityWatchLocked() {
-  if (subchannel_list_->tracer()->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*subchannel_list_->tracer())) {
     gpr_log(GPR_INFO,
             "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
             " (subchannel %p): renewing watch: requesting connectivity change "
@@ -366,7 +365,7 @@
 template <typename SubchannelListType, typename SubchannelDataType>
 void SubchannelData<SubchannelListType,
                     SubchannelDataType>::StopConnectivityWatchLocked() {
-  if (subchannel_list_->tracer()->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*subchannel_list_->tracer())) {
     gpr_log(GPR_INFO,
             "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
             " (subchannel %p): stopping connectivity watch",
@@ -382,7 +381,7 @@
 template <typename SubchannelListType, typename SubchannelDataType>
 void SubchannelData<SubchannelListType, SubchannelDataType>::
     CancelConnectivityWatchLocked(const char* reason) {
-  if (subchannel_list_->tracer()->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*subchannel_list_->tracer())) {
     gpr_log(GPR_INFO,
             "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
             " (subchannel %p): canceling connectivity watch (%s)",
@@ -414,7 +413,7 @@
     // is READY again (e.g., if the subchannel has transitioned back to
     // READY before the next watch gets requested).
     if (connected_subchannel_ == nullptr) {
-      if (subchannel_list_->tracer()->enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(*subchannel_list_->tracer())) {
         gpr_log(GPR_INFO,
                 "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
                 " (subchannel %p): state is READY but connected subchannel is "
@@ -437,7 +436,7 @@
 void SubchannelData<SubchannelListType, SubchannelDataType>::
     OnConnectivityChangedLocked(void* arg, grpc_error* error) {
   SubchannelData* sd = static_cast<SubchannelData*>(arg);
-  if (sd->subchannel_list_->tracer()->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*sd->subchannel_list_->tracer())) {
     gpr_log(
         GPR_INFO,
         "[%s %p] subchannel list %p index %" PRIuPTR " of %" PRIuPTR
@@ -462,8 +461,7 @@
     return;
   }
   // Call the subclass's ProcessConnectivityChangeLocked() method.
-  sd->ProcessConnectivityChangeLocked(sd->pending_connectivity_state_unsafe_,
-                                      GRPC_ERROR_REF(error));
+  sd->ProcessConnectivityChangeLocked(sd->pending_connectivity_state_unsafe_);
 }
 
 template <typename SubchannelListType, typename SubchannelDataType>
@@ -492,7 +490,7 @@
       policy_(policy),
       tracer_(tracer),
       combiner_(GRPC_COMBINER_REF(combiner, "subchannel_list")) {
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO,
             "[%s %p] Creating subchannel list %p for %" PRIuPTR " subchannels",
             tracer_->name(), policy, this, addresses.size());
@@ -508,7 +506,13 @@
                                          GRPC_ARG_INHIBIT_HEALTH_CHECKING};
   // Create a subchannel for each address.
   for (size_t i = 0; i < addresses.size(); i++) {
-    GPR_ASSERT(!addresses[i].IsBalancer());
+    // TODO(roth): we should ideally hide this from the LB policy code. In
+    // principle, if we're dealing with this special case in the client_channel
+    // code for selecting grpclb, then we should also strip out these addresses
+    // there if we're not using grpclb.
+    if (addresses[i].IsBalancer()) {
+      continue;
+    }
     InlinedVector<grpc_arg, 3> args_to_add;
     const size_t subchannel_address_arg_index = args_to_add.size();
     args_to_add.emplace_back(
@@ -526,7 +530,7 @@
     grpc_channel_args_destroy(new_args);
     if (subchannel == nullptr) {
       // Subchannel could not be created.
-      if (tracer_->enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
         char* address_uri = grpc_sockaddr_to_uri(&addresses[i].address());
         gpr_log(GPR_INFO,
                 "[%s %p] could not create subchannel for address uri %s, "
@@ -536,7 +540,7 @@
       }
       continue;
     }
-    if (tracer_->enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
       char* address_uri = grpc_sockaddr_to_uri(&addresses[i].address());
       gpr_log(GPR_INFO,
               "[%s %p] subchannel list %p index %" PRIuPTR
@@ -551,7 +555,7 @@
 
 template <typename SubchannelListType, typename SubchannelDataType>
 SubchannelList<SubchannelListType, SubchannelDataType>::~SubchannelList() {
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO, "[%s %p] Destroying subchannel_list %p", tracer_->name(),
             policy_, this);
   }
@@ -560,7 +564,7 @@
 
 template <typename SubchannelListType, typename SubchannelDataType>
 void SubchannelList<SubchannelListType, SubchannelDataType>::ShutdownLocked() {
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO, "[%s %p] Shutting down subchannel_list %p",
             tracer_->name(), policy_, this);
   }
diff --git a/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc b/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc
index d3b13a6..3eb9437 100644
--- a/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc
@@ -68,7 +68,9 @@
 #include <grpc/support/string_util.h>
 #include <grpc/support/time.h>
 
+#include "include/grpc/support/alloc.h"
 #include "src/core/ext/filters/client_channel/client_channel.h"
+#include "src/core/ext/filters/client_channel/lb_policy.h"
 #include "src/core/ext/filters/client_channel/lb_policy/xds/xds.h"
 #include "src/core/ext/filters/client_channel/lb_policy/xds/xds_channel.h"
 #include "src/core/ext/filters/client_channel/lb_policy/xds/xds_client_stats.h"
@@ -85,10 +87,11 @@
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
+#include "src/core/lib/gprpp/map.h"
 #include "src/core/lib/gprpp/memory.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
 #include "src/core/lib/gprpp/orphanable.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/sockaddr.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
@@ -114,6 +117,35 @@
 namespace {
 
 constexpr char kXds[] = "xds_experimental";
+constexpr char kDefaultLocalityName[] = "xds_default_locality";
+constexpr uint32_t kDefaultLocalityWeight = 3;
+
+class ParsedXdsConfig : public ParsedLoadBalancingConfig {
+ public:
+  ParsedXdsConfig(const char* balancer_name,
+                  RefCountedPtr<ParsedLoadBalancingConfig> child_policy,
+                  RefCountedPtr<ParsedLoadBalancingConfig> fallback_policy)
+      : balancer_name_(balancer_name),
+        child_policy_(std::move(child_policy)),
+        fallback_policy_(std::move(fallback_policy)) {}
+
+  const char* name() const override { return kXds; }
+
+  const char* balancer_name() const { return balancer_name_; };
+
+  RefCountedPtr<ParsedLoadBalancingConfig> child_policy() const {
+    return child_policy_;
+  }
+
+  RefCountedPtr<ParsedLoadBalancingConfig> fallback_policy() const {
+    return fallback_policy_;
+  }
+
+ private:
+  const char* balancer_name_ = nullptr;
+  RefCountedPtr<ParsedLoadBalancingConfig> child_policy_;
+  RefCountedPtr<ParsedLoadBalancingConfig> fallback_policy_;
+};
 
 class XdsLb : public LoadBalancingPolicy {
  public:
@@ -128,6 +160,9 @@
       channelz::ChildRefsList* child_channels) override;
 
  private:
+  struct LocalityServerlistEntry;
+  using LocalityList = InlinedVector<UniquePtr<LocalityServerlistEntry>, 1>;
+
   /// Contains a channel to the LB server and all the data related to the
   /// channel.
   class BalancerChannelState
@@ -232,6 +267,10 @@
     static void OnCallRetryTimerLocked(void* arg, grpc_error* error);
     void StartCallLocked();
 
+    void StartConnectivityWatchLocked();
+    void CancelConnectivityWatchLocked();
+    static void OnConnectivityChangedLocked(void* arg, grpc_error* error);
+
    private:
     // The owning LB policy.
     RefCountedPtr<XdsLb> xdslb_policy_;
@@ -239,6 +278,8 @@
     // The channel and its status.
     grpc_channel* channel_;
     bool shutting_down_ = false;
+    grpc_connectivity_state connectivity_ = GRPC_CHANNEL_IDLE;
+    grpc_closure on_connectivity_changed_;
 
     // The data associated with the current LB call. It holds a ref to this LB
     // channel. It's instantiated every time we query for backends. It's reset
@@ -252,41 +293,151 @@
     bool retry_timer_callback_pending_ = false;
   };
 
+  // Since pickers are UniquePtrs we use this RefCounted wrapper
+  // to control references to it by the xds picker and the locality
+  // entry
+  class PickerRef : public RefCounted<PickerRef> {
+   public:
+    explicit PickerRef(UniquePtr<SubchannelPicker> picker)
+        : picker_(std::move(picker)) {}
+    PickResult Pick(PickArgs* pick, grpc_error** error) {
+      return picker_->Pick(pick, error);
+    }
+
+   private:
+    UniquePtr<SubchannelPicker> picker_;
+  };
+
+  // The picker will use a stateless weighting algorithm to pick the locality to
+  // use for each request.
   class Picker : public SubchannelPicker {
    public:
-    Picker(UniquePtr<SubchannelPicker> child_picker,
-           RefCountedPtr<XdsLbClientStats> client_stats)
-        : child_picker_(std::move(child_picker)),
-          client_stats_(std::move(client_stats)) {}
+    // Maintains a weighted list of pickers from each locality that is in ready
+    // state. The first element in the pair represents the end of a range
+    // proportional to the locality's weight. The start of the range is the
+    // previous value in the vector and is 0 for the first element.
+    using PickerList =
+        InlinedVector<Pair<uint32_t, RefCountedPtr<PickerRef>>, 1>;
+    Picker(RefCountedPtr<XdsLbClientStats> client_stats, PickerList pickers)
+        : client_stats_(std::move(client_stats)),
+          pickers_(std::move(pickers)) {}
 
     PickResult Pick(PickArgs* pick, grpc_error** error) override;
 
    private:
-    UniquePtr<SubchannelPicker> child_picker_;
+    // Calls the picker of the locality that the key falls within
+    PickResult PickFromLocality(const uint32_t key, PickArgs* pick,
+                                grpc_error** error);
     RefCountedPtr<XdsLbClientStats> client_stats_;
+    PickerList pickers_;
   };
 
-  class Helper : public ChannelControlHelper {
+  class FallbackHelper : public ChannelControlHelper {
    public:
-    explicit Helper(RefCountedPtr<XdsLb> parent) : parent_(std::move(parent)) {}
+    explicit FallbackHelper(RefCountedPtr<XdsLb> parent)
+        : parent_(std::move(parent)) {}
 
     Subchannel* CreateSubchannel(const grpc_channel_args& args) override;
     grpc_channel* CreateChannel(const char* target,
                                 const grpc_channel_args& args) override;
-    void UpdateState(grpc_connectivity_state state, grpc_error* state_error,
+    void UpdateState(grpc_connectivity_state state,
                      UniquePtr<SubchannelPicker> picker) override;
     void RequestReresolution() override;
 
     void set_child(LoadBalancingPolicy* child) { child_ = child; }
 
    private:
-    bool CalledByPendingChild() const;
-    bool CalledByCurrentChild() const;
+    bool CalledByPendingFallback() const;
+    bool CalledByCurrentFallback() const;
 
     RefCountedPtr<XdsLb> parent_;
     LoadBalancingPolicy* child_ = nullptr;
   };
 
+  class LocalityMap {
+   public:
+    class LocalityEntry : public InternallyRefCounted<LocalityEntry> {
+     public:
+      LocalityEntry(RefCountedPtr<XdsLb> parent, uint32_t locality_weight)
+          : parent_(std::move(parent)), locality_weight_(locality_weight) {}
+      ~LocalityEntry() = default;
+
+      void UpdateLocked(xds_grpclb_serverlist* serverlist,
+                        ParsedLoadBalancingConfig* child_policy_config,
+                        const grpc_channel_args* args);
+      void ShutdownLocked();
+      void ResetBackoffLocked();
+      void FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels,
+                                    channelz::ChildRefsList* child_channels);
+      void Orphan() override;
+
+     private:
+      class Helper : public ChannelControlHelper {
+       public:
+        explicit Helper(RefCountedPtr<LocalityEntry> entry)
+            : entry_(std::move(entry)) {}
+
+        Subchannel* CreateSubchannel(const grpc_channel_args& args) override;
+        grpc_channel* CreateChannel(const char* target,
+                                    const grpc_channel_args& args) override;
+        void UpdateState(grpc_connectivity_state state,
+                         UniquePtr<SubchannelPicker> picker) override;
+        void RequestReresolution() override;
+        void set_child(LoadBalancingPolicy* child) { child_ = child; }
+
+       private:
+        bool CalledByPendingChild() const;
+        bool CalledByCurrentChild() const;
+
+        RefCountedPtr<LocalityEntry> entry_;
+        LoadBalancingPolicy* child_ = nullptr;
+      };
+      // Methods for dealing with the child policy.
+      OrphanablePtr<LoadBalancingPolicy> CreateChildPolicyLocked(
+          const char* name, const grpc_channel_args* args);
+      grpc_channel_args* CreateChildPolicyArgsLocked(
+          const grpc_channel_args* args);
+
+      OrphanablePtr<LoadBalancingPolicy> child_policy_;
+      OrphanablePtr<LoadBalancingPolicy> pending_child_policy_;
+      // Lock held when modifying the value of child_policy_ or
+      // pending_child_policy_.
+      Mutex child_policy_mu_;
+      RefCountedPtr<XdsLb> parent_;
+      RefCountedPtr<PickerRef> picker_ref_;
+      grpc_connectivity_state connectivity_state_;
+      uint32_t locality_weight_;
+    };
+
+    void UpdateLocked(const LocalityList& locality_list,
+                      ParsedLoadBalancingConfig* child_policy_config,
+                      const grpc_channel_args* args, XdsLb* parent);
+    void ShutdownLocked();
+    void ResetBackoffLocked();
+    void FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels,
+                                  channelz::ChildRefsList* child_channels);
+
+   private:
+    void PruneLocalities(const LocalityList& locality_list);
+    Map<UniquePtr<char>, OrphanablePtr<LocalityEntry>, StringLess> map_;
+    // Lock held while filling child refs for all localities
+    // inside the map
+    Mutex child_refs_mu_;
+  };
+
+  struct LocalityServerlistEntry {
+    ~LocalityServerlistEntry() {
+      gpr_free(locality_name);
+      xds_grpclb_destroy_serverlist(serverlist);
+    }
+
+    char* locality_name;
+    uint32_t locality_weight;
+    // The deserialized response from the balancer. May be nullptr until one
+    // such response has arrived.
+    xds_grpclb_serverlist* serverlist;
+  };
+
   ~XdsLb();
 
   void ShutdownLocked() override;
@@ -299,21 +450,20 @@
   // If parsing succeeds, updates \a balancer_name, and updates \a
   // child_policy_config_ and \a fallback_policy_config_ if they are also
   // found. Does nothing upon failure.
-  void ParseLbConfig(Config* xds_config);
+  void ParseLbConfig(const ParsedXdsConfig* xds_config);
 
   BalancerChannelState* LatestLbChannel() const {
     return pending_lb_chand_ != nullptr ? pending_lb_chand_.get()
                                         : lb_chand_.get();
   }
 
-  // Callback to enter fallback mode.
+  // Methods for dealing with fallback state.
+  void MaybeCancelFallbackAtStartupChecks();
   static void OnFallbackTimerLocked(void* arg, grpc_error* error);
-
-  // Methods for dealing with the child policy.
-  void CreateOrUpdateChildPolicyLocked();
-  grpc_channel_args* CreateChildPolicyArgsLocked();
-  OrphanablePtr<LoadBalancingPolicy> CreateChildPolicyLocked(
+  void UpdateFallbackPolicyLocked();
+  OrphanablePtr<LoadBalancingPolicy> CreateFallbackPolicyLocked(
       const char* name, const grpc_channel_args* args);
+  void MaybeExitFallbackMode();
 
   // Who the client is trying to communicate with.
   const char* server_name_ = nullptr;
@@ -333,33 +483,48 @@
   // Mutex to protect the channel to the LB server. This is used when
   // processing a channelz request.
   // TODO(juanlishen): Replace this with atomic.
-  gpr_mu lb_chand_mu_;
+  Mutex lb_chand_mu_;
 
   // Timeout in milliseconds for the LB call. 0 means no deadline.
   int lb_call_timeout_ms_ = 0;
 
-  // The deserialized response from the balancer. May be nullptr until one
-  // such response has arrived.
-  xds_grpclb_serverlist* serverlist_ = nullptr;
-
+  // Whether the checks for fallback at startup are ALL pending. There are
+  // several cases where this can be reset:
+  // 1. The fallback timer fires, we enter fallback mode.
+  // 2. Before the fallback timer fires, the LB channel becomes
+  // TRANSIENT_FAILURE or the LB call fails, we enter fallback mode.
+  // 3. Before the fallback timer fires, we receive a response from the
+  // balancer, we cancel the fallback timer and use the response to update the
+  // locality map.
+  bool fallback_at_startup_checks_pending_ = false;
   // Timeout in milliseconds for before using fallback backend addresses.
   // 0 means not using fallback.
-  RefCountedPtr<Config> fallback_policy_config_;
   int lb_fallback_timeout_ms_ = 0;
   // The backend addresses from the resolver.
-  UniquePtr<ServerAddressList> fallback_backend_addresses_;
+  ServerAddressList fallback_backend_addresses_;
   // Fallback timer.
-  bool fallback_timer_callback_pending_ = false;
   grpc_timer lb_fallback_timer_;
   grpc_closure lb_on_fallback_;
 
+  // The policy to use for the fallback backends.
+  RefCountedPtr<ParsedLoadBalancingConfig> fallback_policy_config_;
+  // Lock held when modifying the value of fallback_policy_ or
+  // pending_fallback_policy_.
+  Mutex fallback_policy_mu_;
+  // Non-null iff we are in fallback mode.
+  OrphanablePtr<LoadBalancingPolicy> fallback_policy_;
+  OrphanablePtr<LoadBalancingPolicy> pending_fallback_policy_;
+
   // The policy to use for the backends.
-  RefCountedPtr<Config> child_policy_config_;
-  OrphanablePtr<LoadBalancingPolicy> child_policy_;
-  OrphanablePtr<LoadBalancingPolicy> pending_child_policy_;
-  // Lock held when modifying the value of child_policy_ or
-  // pending_child_policy_.
-  gpr_mu child_policy_mu_;
+  RefCountedPtr<ParsedLoadBalancingConfig> child_policy_config_;
+  // Map of policies to use in the backend
+  LocalityMap locality_map_;
+  // TODO(mhaidry) : Add support for multiple maps of localities
+  // with different priorities
+  LocalityList locality_serverlist_;
+  // TODO(mhaidry) : Add a pending locality map that may be swapped with the
+  // the current one when new localities in the pending map are ready
+  // to accept connections
 };
 
 //
@@ -368,8 +533,12 @@
 
 XdsLb::PickResult XdsLb::Picker::Pick(PickArgs* pick, grpc_error** error) {
   // TODO(roth): Add support for drop handling.
-  // Forward pick to child policy.
-  PickResult result = child_picker_->Pick(pick, error);
+  // Generate a random number between 0 and the total weight
+  const uint32_t key =
+      (rand() * pickers_[pickers_.size() - 1].first) / RAND_MAX;
+  // Forward pick to whichever locality maps to the range in which the
+  // random number falls in.
+  PickResult result = PickFromLocality(key, pick, error);
   // If pick succeeded, add client stats.
   if (result == PickResult::PICK_COMPLETE &&
       pick->connected_subchannel != nullptr && client_stats_ != nullptr) {
@@ -378,103 +547,101 @@
   return result;
 }
 
-//
-// XdsLb::Helper
-//
-
-bool XdsLb::Helper::CalledByPendingChild() const {
-  GPR_ASSERT(child_ != nullptr);
-  return child_ == parent_->pending_child_policy_.get();
+XdsLb::PickResult XdsLb::Picker::PickFromLocality(const uint32_t key,
+                                                  PickArgs* pick,
+                                                  grpc_error** error) {
+  size_t mid = 0;
+  size_t start_index = 0;
+  size_t end_index = pickers_.size() - 1;
+  size_t index = 0;
+  while (end_index > start_index) {
+    mid = (start_index + end_index) / 2;
+    if (pickers_[mid].first > key) {
+      end_index = mid;
+    } else if (pickers_[mid].first < key) {
+      start_index = mid + 1;
+    } else {
+      index = mid + 1;
+      break;
+    }
+  }
+  if (index == 0) index = start_index;
+  GPR_ASSERT(pickers_[index].first > key);
+  return pickers_[index].second->Pick(pick, error);
 }
 
-bool XdsLb::Helper::CalledByCurrentChild() const {
+//
+// XdsLb::FallbackHelper
+//
+
+bool XdsLb::FallbackHelper::CalledByPendingFallback() const {
   GPR_ASSERT(child_ != nullptr);
-  return child_ == parent_->child_policy_.get();
+  return child_ == parent_->pending_fallback_policy_.get();
 }
 
-Subchannel* XdsLb::Helper::CreateSubchannel(const grpc_channel_args& args) {
+bool XdsLb::FallbackHelper::CalledByCurrentFallback() const {
+  GPR_ASSERT(child_ != nullptr);
+  return child_ == parent_->fallback_policy_.get();
+}
+
+Subchannel* XdsLb::FallbackHelper::CreateSubchannel(
+    const grpc_channel_args& args) {
   if (parent_->shutting_down_ ||
-      (!CalledByPendingChild() && !CalledByCurrentChild())) {
+      (!CalledByPendingFallback() && !CalledByCurrentFallback())) {
     return nullptr;
   }
   return parent_->channel_control_helper()->CreateSubchannel(args);
 }
 
-grpc_channel* XdsLb::Helper::CreateChannel(const char* target,
-                                           const grpc_channel_args& args) {
+grpc_channel* XdsLb::FallbackHelper::CreateChannel(
+    const char* target, const grpc_channel_args& args) {
   if (parent_->shutting_down_ ||
-      (!CalledByPendingChild() && !CalledByCurrentChild())) {
+      (!CalledByPendingFallback() && !CalledByCurrentFallback())) {
     return nullptr;
   }
   return parent_->channel_control_helper()->CreateChannel(target, args);
 }
 
-void XdsLb::Helper::UpdateState(grpc_connectivity_state state,
-                                grpc_error* state_error,
-                                UniquePtr<SubchannelPicker> picker) {
-  if (parent_->shutting_down_) {
-    GRPC_ERROR_UNREF(state_error);
-    return;
-  }
-  // If this request is from the pending child policy, ignore it until
+void XdsLb::FallbackHelper::UpdateState(grpc_connectivity_state state,
+                                        UniquePtr<SubchannelPicker> picker) {
+  if (parent_->shutting_down_) return;
+  // If this request is from the pending fallback policy, ignore it until
   // it reports READY, at which point we swap it into place.
-  if (CalledByPendingChild()) {
-    if (grpc_lb_xds_trace.enabled()) {
-      gpr_log(GPR_INFO,
-              "[xdslb %p helper %p] pending child policy %p reports state=%s",
-              parent_.get(), this, parent_->pending_child_policy_.get(),
-              grpc_connectivity_state_name(state));
+  if (CalledByPendingFallback()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+      gpr_log(
+          GPR_INFO,
+          "[xdslb %p helper %p] pending fallback policy %p reports state=%s",
+          parent_.get(), this, parent_->pending_fallback_policy_.get(),
+          grpc_connectivity_state_name(state));
     }
-    if (state != GRPC_CHANNEL_READY) {
-      GRPC_ERROR_UNREF(state_error);
-      return;
-    }
+    if (state != GRPC_CHANNEL_READY) return;
     grpc_pollset_set_del_pollset_set(
-        parent_->child_policy_->interested_parties(),
+        parent_->fallback_policy_->interested_parties(),
         parent_->interested_parties());
-    MutexLock lock(&parent_->child_policy_mu_);
-    parent_->child_policy_ = std::move(parent_->pending_child_policy_);
-  } else if (!CalledByCurrentChild()) {
-    // This request is from an outdated child, so ignore it.
-    GRPC_ERROR_UNREF(state_error);
+    MutexLock lock(&parent_->fallback_policy_mu_);
+    parent_->fallback_policy_ = std::move(parent_->pending_fallback_policy_);
+  } else if (!CalledByCurrentFallback()) {
+    // This request is from an outdated fallback policy, so ignore it.
     return;
   }
-  // TODO(juanlishen): When in fallback mode, pass the child picker
-  // through without wrapping it.  (Or maybe use a different helper for
-  // the fallback policy?)
-  GPR_ASSERT(parent_->lb_chand_ != nullptr);
-  RefCountedPtr<XdsLbClientStats> client_stats =
-      parent_->lb_chand_->lb_calld() == nullptr
-          ? nullptr
-          : parent_->lb_chand_->lb_calld()->client_stats();
-  parent_->channel_control_helper()->UpdateState(
-      state, state_error,
-      UniquePtr<SubchannelPicker>(
-          New<Picker>(std::move(picker), std::move(client_stats))));
+  parent_->channel_control_helper()->UpdateState(state, std::move(picker));
 }
 
-void XdsLb::Helper::RequestReresolution() {
+void XdsLb::FallbackHelper::RequestReresolution() {
   if (parent_->shutting_down_) return;
-  // If there is a pending child policy, ignore re-resolution requests
-  // from the current child policy (or any outdated child).
-  if (parent_->pending_child_policy_ != nullptr && !CalledByPendingChild()) {
-    return;
-  }
-  if (grpc_lb_xds_trace.enabled()) {
+  const LoadBalancingPolicy* latest_fallback_policy =
+      parent_->pending_fallback_policy_ != nullptr
+          ? parent_->pending_fallback_policy_.get()
+          : parent_->fallback_policy_.get();
+  if (child_ != latest_fallback_policy) return;
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     gpr_log(GPR_INFO,
-            "[xdslb %p] Re-resolution requested from the internal RR policy "
-            "(%p).",
-            parent_.get(), parent_->child_policy_.get());
+            "[xdslb %p] Re-resolution requested from the fallback policy (%p).",
+            parent_.get(), child_);
   }
   GPR_ASSERT(parent_->lb_chand_ != nullptr);
-  // If we are talking to a balancer, we expect to get updated addresses
-  // from the balancer, so we can ignore the re-resolution request from
-  // the child policy. Otherwise, pass the re-resolution request up to the
-  // channel.
-  if (parent_->lb_chand_->lb_calld() == nullptr ||
-      !parent_->lb_chand_->lb_calld()->seen_initial_response()) {
-    parent_->channel_control_helper()->RequestReresolution();
-  }
+  parent_->channel_control_helper()->RequestReresolution();
 }
 
 //
@@ -482,12 +649,11 @@
 //
 
 // Returns the backend addresses extracted from the given addresses.
-UniquePtr<ServerAddressList> ExtractBackendAddresses(
-    const ServerAddressList& addresses) {
-  auto backend_addresses = MakeUnique<ServerAddressList>();
+ServerAddressList ExtractBackendAddresses(const ServerAddressList& addresses) {
+  ServerAddressList backend_addresses;
   for (size_t i = 0; i < addresses.size(); ++i) {
     if (!addresses[i].IsBalancer()) {
-      backend_addresses->emplace_back(addresses[i]);
+      backend_addresses.emplace_back(addresses[i]);
     }
   }
   return backend_addresses;
@@ -567,6 +733,9 @@
               .set_multiplier(GRPC_XDS_RECONNECT_BACKOFF_MULTIPLIER)
               .set_jitter(GRPC_XDS_RECONNECT_JITTER)
               .set_max_backoff(GRPC_XDS_RECONNECT_MAX_BACKOFF_SECONDS * 1000)) {
+  GRPC_CLOSURE_INIT(&on_connectivity_changed_,
+                    &XdsLb::BalancerChannelState::OnConnectivityChangedLocked,
+                    this, grpc_combiner_scheduler(xdslb_policy_->combiner()));
   channel_ = xdslb_policy_->channel_control_helper()->CreateChannel(
       balancer_name, args);
   GPR_ASSERT(channel_ != nullptr);
@@ -586,7 +755,7 @@
 
 void XdsLb::BalancerChannelState::StartCallRetryTimerLocked() {
   grpc_millis next_try = lb_call_backoff_.NextAttemptTime();
-  if (grpc_lb_xds_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     gpr_log(GPR_INFO,
             "[xdslb %p] Failed to connect to LB server (lb_chand: %p)...",
             xdslb_policy_.get(), this);
@@ -612,7 +781,7 @@
   lb_chand->retry_timer_callback_pending_ = false;
   if (!lb_chand->shutting_down_ && error == GRPC_ERROR_NONE &&
       lb_chand->lb_calld_ == nullptr) {
-    if (grpc_lb_xds_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
       gpr_log(GPR_INFO,
               "[xdslb %p] Restarting call to LB server (lb_chand: %p)",
               lb_chand->xdslb_policy_.get(), lb_chand);
@@ -627,7 +796,7 @@
   GPR_ASSERT(channel_ != nullptr);
   GPR_ASSERT(lb_calld_ == nullptr);
   lb_calld_ = MakeOrphanable<BalancerCallState>(Ref());
-  if (grpc_lb_xds_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     gpr_log(GPR_INFO,
             "[xdslb %p] Query for backends (lb_chand: %p, lb_calld: %p)",
             xdslb_policy_.get(), this, lb_calld_.get());
@@ -635,6 +804,62 @@
   lb_calld_->StartQuery();
 }
 
+void XdsLb::BalancerChannelState::StartConnectivityWatchLocked() {
+  grpc_channel_element* client_channel_elem =
+      grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel_));
+  GPR_ASSERT(client_channel_elem->filter == &grpc_client_channel_filter);
+  // Ref held by callback.
+  Ref(DEBUG_LOCATION, "watch_lb_channel_connectivity").release();
+  grpc_client_channel_watch_connectivity_state(
+      client_channel_elem,
+      grpc_polling_entity_create_from_pollset_set(
+          xdslb_policy_->interested_parties()),
+      &connectivity_, &on_connectivity_changed_, nullptr);
+}
+
+void XdsLb::BalancerChannelState::CancelConnectivityWatchLocked() {
+  grpc_channel_element* client_channel_elem =
+      grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel_));
+  GPR_ASSERT(client_channel_elem->filter == &grpc_client_channel_filter);
+  grpc_client_channel_watch_connectivity_state(
+      client_channel_elem,
+      grpc_polling_entity_create_from_pollset_set(
+          xdslb_policy_->interested_parties()),
+      nullptr, &on_connectivity_changed_, nullptr);
+}
+
+void XdsLb::BalancerChannelState::OnConnectivityChangedLocked(
+    void* arg, grpc_error* error) {
+  BalancerChannelState* self = static_cast<BalancerChannelState*>(arg);
+  if (!self->shutting_down_ &&
+      self->xdslb_policy_->fallback_at_startup_checks_pending_) {
+    if (self->connectivity_ != GRPC_CHANNEL_TRANSIENT_FAILURE) {
+      // Not in TRANSIENT_FAILURE.  Renew connectivity watch.
+      grpc_channel_element* client_channel_elem =
+          grpc_channel_stack_last_element(
+              grpc_channel_get_channel_stack(self->channel_));
+      GPR_ASSERT(client_channel_elem->filter == &grpc_client_channel_filter);
+      grpc_client_channel_watch_connectivity_state(
+          client_channel_elem,
+          grpc_polling_entity_create_from_pollset_set(
+              self->xdslb_policy_->interested_parties()),
+          &self->connectivity_, &self->on_connectivity_changed_, nullptr);
+      return;  // Early out so we don't drop the ref below.
+    }
+    // In TRANSIENT_FAILURE.  Cancel the fallback timer and go into
+    // fallback mode immediately.
+    gpr_log(GPR_INFO,
+            "[xdslb %p] Balancer channel in state TRANSIENT_FAILURE; "
+            "entering fallback mode",
+            self);
+    self->xdslb_policy_->fallback_at_startup_checks_pending_ = false;
+    grpc_timer_cancel(&self->xdslb_policy_->lb_fallback_timer_);
+    self->xdslb_policy_->UpdateFallbackPolicyLocked();
+  }
+  // Done watching connectivity state, so drop ref.
+  self->Unref(DEBUG_LOCATION, "watch_lb_channel_connectivity");
+}
+
 //
 // XdsLb::BalancerChannelState::BalancerCallState
 //
@@ -707,7 +932,7 @@
 
 void XdsLb::BalancerChannelState::BalancerCallState::StartQuery() {
   GPR_ASSERT(lb_call_ != nullptr);
-  if (grpc_lb_xds_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     gpr_log(GPR_INFO, "[xdslb %p] Starting LB call (lb_calld: %p, lb_call: %p)",
             xdslb_policy(), this, lb_call_);
   }
@@ -880,6 +1105,14 @@
       (initial_response = xds_grpclb_initial_response_parse(response_slice)) !=
           nullptr) {
     // Have NOT seen initial response, look for initial response.
+    // TODO(juanlishen): When we convert this to use the xds protocol, the
+    // balancer will send us a fallback timeout such that we should go into
+    // fallback mode if we have lost contact with the balancer after a certain
+    // period of time. We will need to save the timeout value here, and then
+    // when the balancer call ends, we will need to start a timer for the
+    // specified period of time, and if the timer fires, we go into fallback
+    // mode. We will also need to cancel the timer when we receive a serverlist
+    // from the balancer.
     if (initial_response->has_client_stats_report_interval) {
       const grpc_millis interval = xds_grpclb_duration_to_millis(
           &initial_response->client_stats_report_interval);
@@ -888,7 +1121,7 @@
             GPR_MAX(GPR_MS_PER_SEC, interval);
       }
     }
-    if (grpc_lb_xds_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
       if (lb_calld->client_stats_report_interval_ != 0) {
         gpr_log(GPR_INFO,
                 "[xdslb %p] Received initial LB response message; "
@@ -907,7 +1140,7 @@
                   response_slice)) != nullptr) {
     // Have seen initial response, look for serverlist.
     GPR_ASSERT(lb_calld->lb_call_ != nullptr);
-    if (grpc_lb_xds_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
       gpr_log(GPR_INFO,
               "[xdslb %p] Serverlist with %" PRIuPTR " servers received",
               xdslb_policy, serverlist->num_servers);
@@ -921,67 +1154,69 @@
         gpr_free(ipport);
       }
     }
-    /* update serverlist */
-    // TODO(juanlishen): Don't ingore empty serverlist.
-    if (serverlist->num_servers > 0) {
-      // Pending LB channel receives a serverlist; promote it.
-      // Note that this call can't be on a discarded pending channel, because
-      // such channels don't have any current call but we have checked this call
-      // is a current call.
-      if (!lb_calld->lb_chand_->IsCurrentChannel()) {
-        if (grpc_lb_xds_trace.enabled()) {
-          gpr_log(GPR_INFO,
-                  "[xdslb %p] Promoting pending LB channel %p to replace "
-                  "current LB channel %p",
-                  xdslb_policy, lb_calld->lb_chand_.get(),
-                  lb_calld->xdslb_policy()->lb_chand_.get());
-        }
-        lb_calld->xdslb_policy()->lb_chand_ =
-            std::move(lb_calld->xdslb_policy()->pending_lb_chand_);
+    // Pending LB channel receives a serverlist; promote it.
+    // Note that this call can't be on a discarded pending channel, because
+    // such channels don't have any current call but we have checked this call
+    // is a current call.
+    if (!lb_calld->lb_chand_->IsCurrentChannel()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+        gpr_log(GPR_INFO,
+                "[xdslb %p] Promoting pending LB channel %p to replace "
+                "current LB channel %p",
+                xdslb_policy, lb_calld->lb_chand_.get(),
+                lb_calld->xdslb_policy()->lb_chand_.get());
       }
-      // Start sending client load report only after we start using the
-      // serverlist returned from the current LB call.
-      if (lb_calld->client_stats_report_interval_ > 0 &&
-          lb_calld->client_stats_ == nullptr) {
-        lb_calld->client_stats_ = MakeRefCounted<XdsLbClientStats>();
-        // TODO(roth): We currently track this ref manually.  Once the
-        // ClosureRef API is ready, we should pass the RefCountedPtr<> along
-        // with the callback.
-        auto self = lb_calld->Ref(DEBUG_LOCATION, "client_load_report");
-        self.release();
-        lb_calld->ScheduleNextClientLoadReportLocked();
-      }
-      if (xds_grpclb_serverlist_equals(xdslb_policy->serverlist_, serverlist)) {
-        if (grpc_lb_xds_trace.enabled()) {
-          gpr_log(GPR_INFO,
-                  "[xdslb %p] Incoming server list identical to current, "
-                  "ignoring.",
-                  xdslb_policy);
-        }
-        xds_grpclb_destroy_serverlist(serverlist);
-      } else { /* new serverlist */
-        if (xdslb_policy->serverlist_ != nullptr) {
-          /* dispose of the old serverlist */
-          xds_grpclb_destroy_serverlist(xdslb_policy->serverlist_);
-        } else {
-          /* or dispose of the fallback */
-          xdslb_policy->fallback_backend_addresses_.reset();
-          if (xdslb_policy->fallback_timer_callback_pending_) {
-            grpc_timer_cancel(&xdslb_policy->lb_fallback_timer_);
-          }
-        }
-        // and update the copy in the XdsLb instance. This
-        // serverlist instance will be destroyed either upon the next
-        // update or when the XdsLb instance is destroyed.
-        xdslb_policy->serverlist_ = serverlist;
-        xdslb_policy->CreateOrUpdateChildPolicyLocked();
-      }
-    } else {
-      if (grpc_lb_xds_trace.enabled()) {
-        gpr_log(GPR_INFO, "[xdslb %p] Received empty server list, ignoring.",
+      lb_calld->xdslb_policy()->lb_chand_ =
+          std::move(lb_calld->xdslb_policy()->pending_lb_chand_);
+    }
+    // Start sending client load report only after we start using the
+    // serverlist returned from the current LB call.
+    if (lb_calld->client_stats_report_interval_ > 0 &&
+        lb_calld->client_stats_ == nullptr) {
+      lb_calld->client_stats_ = MakeRefCounted<XdsLbClientStats>();
+      lb_calld->Ref(DEBUG_LOCATION, "client_load_report").release();
+      lb_calld->ScheduleNextClientLoadReportLocked();
+    }
+    if (!xdslb_policy->locality_serverlist_.empty() &&
+        xds_grpclb_serverlist_equals(
+            xdslb_policy->locality_serverlist_[0]->serverlist, serverlist)) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+        gpr_log(GPR_INFO,
+                "[xdslb %p] Incoming server list identical to current, "
+                "ignoring.",
                 xdslb_policy);
       }
       xds_grpclb_destroy_serverlist(serverlist);
+    } else {  // New serverlist.
+      // If the balancer tells us to drop all the calls, we should exit fallback
+      // mode immediately.
+      // TODO(juanlishen): When we add EDS drop, we should change to check
+      // drop_percentage.
+      if (serverlist->num_servers == 0) xdslb_policy->MaybeExitFallbackMode();
+      if (!xdslb_policy->locality_serverlist_.empty()) {
+        xds_grpclb_destroy_serverlist(
+            xdslb_policy->locality_serverlist_[0]->serverlist);
+      } else {
+        // This is the first serverlist we've received, don't enter fallback
+        // mode.
+        xdslb_policy->MaybeCancelFallbackAtStartupChecks();
+        // Initialize locality serverlist, currently the list only handles
+        // one child.
+        xdslb_policy->locality_serverlist_.emplace_back(
+            MakeUnique<LocalityServerlistEntry>());
+        xdslb_policy->locality_serverlist_[0]->locality_name =
+            static_cast<char*>(gpr_strdup(kDefaultLocalityName));
+        xdslb_policy->locality_serverlist_[0]->locality_weight =
+            kDefaultLocalityWeight;
+      }
+      // Update the serverlist in the XdsLb instance. This serverlist
+      // instance will be destroyed either upon the next update or when the
+      // XdsLb instance is destroyed.
+      xdslb_policy->locality_serverlist_[0]->serverlist = serverlist;
+      xdslb_policy->locality_map_.UpdateLocked(
+          xdslb_policy->locality_serverlist_,
+          xdslb_policy->child_policy_config_.get(), xdslb_policy->args_,
+          xdslb_policy);
     }
   } else {
     // No valid initial response or serverlist found.
@@ -1017,7 +1252,7 @@
   XdsLb* xdslb_policy = lb_calld->xdslb_policy();
   BalancerChannelState* lb_chand = lb_calld->lb_chand_.get();
   GPR_ASSERT(lb_calld->lb_call_ != nullptr);
-  if (grpc_lb_xds_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     char* status_details =
         grpc_slice_to_c_string(lb_calld->lb_call_status_details_);
     gpr_log(GPR_INFO,
@@ -1036,7 +1271,7 @@
     if (lb_chand != xdslb_policy->LatestLbChannel()) {
       // This channel must be the current one and there is a pending one. Swap
       // in the pending one and we are done.
-      if (grpc_lb_xds_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
         gpr_log(GPR_INFO,
                 "[xdslb %p] Promoting pending LB channel %p to replace "
                 "current LB channel %p",
@@ -1058,6 +1293,18 @@
         lb_chand->StartCallRetryTimerLocked();
       }
       xdslb_policy->channel_control_helper()->RequestReresolution();
+      // If the fallback-at-startup checks are pending, go into fallback mode
+      // immediately.  This short-circuits the timeout for the
+      // fallback-at-startup case.
+      if (xdslb_policy->fallback_at_startup_checks_pending_) {
+        gpr_log(GPR_INFO,
+                "[xdslb %p] Balancer call finished; entering fallback mode",
+                xdslb_policy);
+        xdslb_policy->fallback_at_startup_checks_pending_ = false;
+        grpc_timer_cancel(&xdslb_policy->lb_fallback_timer_);
+        lb_chand->CancelConnectivityWatchLocked();
+        xdslb_policy->UpdateFallbackPolicyLocked();
+      }
     }
   }
   lb_calld->Unref(DEBUG_LOCATION, "lb_call_ended");
@@ -1112,9 +1359,10 @@
 // ctor and dtor
 //
 
-XdsLb::XdsLb(Args args) : LoadBalancingPolicy(std::move(args)) {
-  gpr_mu_init(&lb_chand_mu_);
-  gpr_mu_init(&child_policy_mu_);
+XdsLb::XdsLb(Args args)
+    : LoadBalancingPolicy(std::move(args)),
+      locality_map_(),
+      locality_serverlist_() {
   // Record server name.
   const grpc_arg* arg = grpc_channel_args_find(args.args, GRPC_ARG_SERVER_URI);
   const char* server_uri = grpc_channel_arg_get_string(arg);
@@ -1122,7 +1370,7 @@
   grpc_uri* uri = grpc_uri_parse(server_uri, true);
   GPR_ASSERT(uri->path[0] != '\0');
   server_name_ = gpr_strdup(uri->path[0] == '/' ? uri->path + 1 : uri->path);
-  if (grpc_lb_xds_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     gpr_log(GPR_INFO,
             "[xdslb %p] Will use '%s' as the server name for LB request.", this,
             server_name_);
@@ -1132,43 +1380,38 @@
   arg = grpc_channel_args_find(args.args, GRPC_ARG_GRPCLB_CALL_TIMEOUT_MS);
   lb_call_timeout_ms_ = grpc_channel_arg_get_integer(arg, {0, 0, INT_MAX});
   // Record fallback timeout.
-  arg = grpc_channel_args_find(args.args, GRPC_ARG_GRPCLB_FALLBACK_TIMEOUT_MS);
+  arg = grpc_channel_args_find(args.args, GRPC_ARG_XDS_FALLBACK_TIMEOUT_MS);
   lb_fallback_timeout_ms_ = grpc_channel_arg_get_integer(
       arg, {GRPC_XDS_DEFAULT_FALLBACK_TIMEOUT_MS, 0, INT_MAX});
 }
 
 XdsLb::~XdsLb() {
-  gpr_mu_destroy(&lb_chand_mu_);
   gpr_free((void*)server_name_);
   grpc_channel_args_destroy(args_);
-  if (serverlist_ != nullptr) {
-    xds_grpclb_destroy_serverlist(serverlist_);
-  }
-  gpr_mu_destroy(&child_policy_mu_);
+  locality_serverlist_.clear();
 }
 
 void XdsLb::ShutdownLocked() {
   shutting_down_ = true;
-  if (fallback_timer_callback_pending_) {
+  if (fallback_at_startup_checks_pending_) {
     grpc_timer_cancel(&lb_fallback_timer_);
   }
-  if (child_policy_ != nullptr) {
-    grpc_pollset_set_del_pollset_set(child_policy_->interested_parties(),
+  locality_map_.ShutdownLocked();
+  if (fallback_policy_ != nullptr) {
+    grpc_pollset_set_del_pollset_set(fallback_policy_->interested_parties(),
                                      interested_parties());
   }
-  if (pending_child_policy_ != nullptr) {
+  if (pending_fallback_policy_ != nullptr) {
     grpc_pollset_set_del_pollset_set(
-        pending_child_policy_->interested_parties(), interested_parties());
+        pending_fallback_policy_->interested_parties(), interested_parties());
   }
   {
-    MutexLock lock(&child_policy_mu_);
-    child_policy_.reset();
-    pending_child_policy_.reset();
+    MutexLock lock(&fallback_policy_mu_);
+    fallback_policy_.reset();
+    pending_fallback_policy_.reset();
   }
-  // We destroy the LB channel here instead of in our destructor because
-  // destroying the channel triggers a last callback to
-  // OnBalancerChannelConnectivityChangedLocked(), and we need to be
-  // alive when that callback is invoked.
+  // We reset the LB channels here instead of in our destructor because they
+  // hold refs to XdsLb.
   {
     MutexLock lock(&lb_chand_mu_);
     lb_chand_.reset();
@@ -1187,28 +1430,30 @@
   if (pending_lb_chand_ != nullptr) {
     grpc_channel_reset_connect_backoff(pending_lb_chand_->channel());
   }
-  if (child_policy_ != nullptr) {
-    child_policy_->ResetBackoffLocked();
+  locality_map_.ResetBackoffLocked();
+  if (fallback_policy_ != nullptr) {
+    fallback_policy_->ResetBackoffLocked();
   }
-  if (pending_child_policy_ != nullptr) {
-    pending_child_policy_->ResetBackoffLocked();
+  if (pending_fallback_policy_ != nullptr) {
+    pending_fallback_policy_->ResetBackoffLocked();
   }
 }
 
 void XdsLb::FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels,
                                      channelz::ChildRefsList* child_channels) {
+  // Delegate to the locality_map_ to fill the children subchannels.
+  locality_map_.FillChildRefsForChannelz(child_subchannels, child_channels);
   {
-    // Delegate to the child_policy_ to fill the children subchannels.
-    // This must be done holding child_policy_mu_, since this method does not
+    // This must be done holding fallback_policy_mu_, since this method does not
     // run in the combiner.
-    MutexLock lock(&child_policy_mu_);
-    if (child_policy_ != nullptr) {
-      child_policy_->FillChildRefsForChannelz(child_subchannels,
-                                              child_channels);
+    MutexLock lock(&fallback_policy_mu_);
+    if (fallback_policy_ != nullptr) {
+      fallback_policy_->FillChildRefsForChannelz(child_subchannels,
+                                                 child_channels);
     }
-    if (pending_child_policy_ != nullptr) {
-      pending_child_policy_->FillChildRefsForChannelz(child_subchannels,
-                                                      child_channels);
+    if (pending_fallback_policy_ != nullptr) {
+      pending_fallback_policy_->FillChildRefsForChannelz(child_subchannels,
+                                                         child_channels);
     }
   }
   MutexLock lock(&lb_chand_mu_);
@@ -1267,96 +1512,290 @@
   grpc_channel_args_destroy(lb_channel_args);
 }
 
-void XdsLb::ParseLbConfig(Config* xds_config) {
-  const grpc_json* xds_config_json = xds_config->config();
-  const char* balancer_name = nullptr;
-  grpc_json* child_policy = nullptr;
-  grpc_json* fallback_policy = nullptr;
-  for (const grpc_json* field = xds_config_json; field != nullptr;
-       field = field->next) {
-    if (field->key == nullptr) return;
-    if (strcmp(field->key, "balancerName") == 0) {
-      if (balancer_name != nullptr) return;  // Duplicate.
-      if (field->type != GRPC_JSON_STRING) return;
-      balancer_name = field->value;
-    } else if (strcmp(field->key, "childPolicy") == 0) {
-      if (child_policy != nullptr) return;  // Duplicate.
-      child_policy = ParseLoadBalancingConfig(field);
-    } else if (strcmp(field->key, "fallbackPolicy") == 0) {
-      if (fallback_policy != nullptr) return;  // Duplicate.
-      fallback_policy = ParseLoadBalancingConfig(field);
-    }
-  }
-  if (balancer_name == nullptr) return;  // Required field.
-  balancer_name_ = UniquePtr<char>(gpr_strdup(balancer_name));
-  if (child_policy != nullptr) {
-    child_policy_config_ =
-        MakeRefCounted<Config>(child_policy, xds_config->service_config());
-  }
-  if (fallback_policy != nullptr) {
-    fallback_policy_config_ =
-        MakeRefCounted<Config>(fallback_policy, xds_config->service_config());
-  }
+void XdsLb::ParseLbConfig(const ParsedXdsConfig* xds_config) {
+  if (xds_config == nullptr || xds_config->balancer_name() == nullptr) return;
+  // TODO(yashykt) : does this need to be a gpr_strdup
+  balancer_name_ = UniquePtr<char>(gpr_strdup(xds_config->balancer_name()));
+  child_policy_config_ = xds_config->child_policy();
+  fallback_policy_config_ = xds_config->fallback_policy();
 }
 
 void XdsLb::UpdateLocked(UpdateArgs args) {
   const bool is_initial_update = lb_chand_ == nullptr;
-  ParseLbConfig(args.config.get());
-  // TODO(juanlishen): Pass fallback policy config update after fallback policy
-  // is added.
+  ParseLbConfig(static_cast<const ParsedXdsConfig*>(args.config.get()));
   if (balancer_name_ == nullptr) {
     gpr_log(GPR_ERROR, "[xdslb %p] LB config parsing fails.", this);
     return;
   }
   ProcessAddressesAndChannelArgsLocked(args.addresses, *args.args);
-  // Update the existing child policy.
-  // Note: We have disabled fallback mode in the code, so this child policy must
-  // have been created from a serverlist.
-  // TODO(vpowar): Handle the fallback_address changes when we add support for
-  // fallback in xDS.
-  if (child_policy_ != nullptr) CreateOrUpdateChildPolicyLocked();
-  // If this is the initial update, start the fallback timer.
+  locality_map_.UpdateLocked(locality_serverlist_, child_policy_config_.get(),
+                             args_, this);
+  // Update the existing fallback policy. The fallback policy config and/or the
+  // fallback addresses may be new.
+  if (fallback_policy_ != nullptr) UpdateFallbackPolicyLocked();
+  // If this is the initial update, start the fallback-at-startup checks.
   if (is_initial_update) {
-    if (lb_fallback_timeout_ms_ > 0 && serverlist_ == nullptr &&
-        !fallback_timer_callback_pending_) {
-      grpc_millis deadline = ExecCtx::Get()->Now() + lb_fallback_timeout_ms_;
-      Ref(DEBUG_LOCATION, "on_fallback_timer").release();  // Held by closure
-      GRPC_CLOSURE_INIT(&lb_on_fallback_, &XdsLb::OnFallbackTimerLocked, this,
-                        grpc_combiner_scheduler(combiner()));
-      fallback_timer_callback_pending_ = true;
-      grpc_timer_init(&lb_fallback_timer_, deadline, &lb_on_fallback_);
-      // TODO(juanlishen): Monitor the connectivity state of the balancer
-      // channel.  If the channel reports TRANSIENT_FAILURE before the
-      // fallback timeout expires, go into fallback mode early.
-    }
+    grpc_millis deadline = ExecCtx::Get()->Now() + lb_fallback_timeout_ms_;
+    Ref(DEBUG_LOCATION, "on_fallback_timer").release();  // Held by closure
+    GRPC_CLOSURE_INIT(&lb_on_fallback_, &XdsLb::OnFallbackTimerLocked, this,
+                      grpc_combiner_scheduler(combiner()));
+    fallback_at_startup_checks_pending_ = true;
+    grpc_timer_init(&lb_fallback_timer_, deadline, &lb_on_fallback_);
+    // Start watching the channel's connectivity state.  If the channel
+    // goes into state TRANSIENT_FAILURE, we go into fallback mode even if
+    // the fallback timeout has not elapsed.
+    lb_chand_->StartConnectivityWatchLocked();
   }
 }
 
 //
-// code for balancer channel and call
+// fallback-related methods
 //
 
+void XdsLb::MaybeCancelFallbackAtStartupChecks() {
+  if (!fallback_at_startup_checks_pending_) return;
+  gpr_log(GPR_INFO,
+          "[xdslb %p] Cancelling fallback timer and LB channel connectivity "
+          "watch",
+          this);
+  grpc_timer_cancel(&lb_fallback_timer_);
+  lb_chand_->CancelConnectivityWatchLocked();
+  fallback_at_startup_checks_pending_ = false;
+}
+
 void XdsLb::OnFallbackTimerLocked(void* arg, grpc_error* error) {
   XdsLb* xdslb_policy = static_cast<XdsLb*>(arg);
-  xdslb_policy->fallback_timer_callback_pending_ = false;
-  // If we receive a serverlist after the timer fires but before this callback
-  // actually runs, don't fall back.
-  if (xdslb_policy->serverlist_ == nullptr && !xdslb_policy->shutting_down_ &&
-      error == GRPC_ERROR_NONE) {
-    if (grpc_lb_xds_trace.enabled()) {
+  // If some fallback-at-startup check is done after the timer fires but before
+  // this callback actually runs, don't fall back.
+  if (xdslb_policy->fallback_at_startup_checks_pending_ &&
+      !xdslb_policy->shutting_down_ && error == GRPC_ERROR_NONE) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
       gpr_log(GPR_INFO,
-              "[xdslb %p] Fallback timer fired. Not using fallback backends",
+              "[xdslb %p] Child policy not ready after fallback timeout; "
+              "entering fallback mode",
               xdslb_policy);
     }
+    xdslb_policy->fallback_at_startup_checks_pending_ = false;
+    xdslb_policy->UpdateFallbackPolicyLocked();
+    xdslb_policy->lb_chand_->CancelConnectivityWatchLocked();
   }
   xdslb_policy->Unref(DEBUG_LOCATION, "on_fallback_timer");
 }
 
+void XdsLb::UpdateFallbackPolicyLocked() {
+  if (shutting_down_) return;
+  // Construct update args.
+  UpdateArgs update_args;
+  update_args.addresses = fallback_backend_addresses_;
+  update_args.config = fallback_policy_config_ == nullptr
+                           ? nullptr
+                           : fallback_policy_config_->Ref();
+  update_args.args = grpc_channel_args_copy(args_);
+  // If the child policy name changes, we need to create a new child
+  // policy.  When this happens, we leave child_policy_ as-is and store
+  // the new child policy in pending_child_policy_.  Once the new child
+  // policy transitions into state READY, we swap it into child_policy_,
+  // replacing the original child policy.  So pending_child_policy_ is
+  // non-null only between when we apply an update that changes the child
+  // policy name and when the new child reports state READY.
+  //
+  // Updates can arrive at any point during this transition.  We always
+  // apply updates relative to the most recently created child policy,
+  // even if the most recent one is still in pending_child_policy_.  This
+  // is true both when applying the updates to an existing child policy
+  // and when determining whether we need to create a new policy.
+  //
+  // As a result of this, there are several cases to consider here:
+  //
+  // 1. We have no existing child policy (i.e., we have started up but
+  //    have not yet received a serverlist from the balancer or gone
+  //    into fallback mode; in this case, both child_policy_ and
+  //    pending_child_policy_ are null).  In this case, we create a
+  //    new child policy and store it in child_policy_.
+  //
+  // 2. We have an existing child policy and have no pending child policy
+  //    from a previous update (i.e., either there has not been a
+  //    previous update that changed the policy name, or we have already
+  //    finished swapping in the new policy; in this case, child_policy_
+  //    is non-null but pending_child_policy_ is null).  In this case:
+  //    a. If child_policy_->name() equals child_policy_name, then we
+  //       update the existing child policy.
+  //    b. If child_policy_->name() does not equal child_policy_name,
+  //       we create a new policy.  The policy will be stored in
+  //       pending_child_policy_ and will later be swapped into
+  //       child_policy_ by the helper when the new child transitions
+  //       into state READY.
+  //
+  // 3. We have an existing child policy and have a pending child policy
+  //    from a previous update (i.e., a previous update set
+  //    pending_child_policy_ as per case 2b above and that policy has
+  //    not yet transitioned into state READY and been swapped into
+  //    child_policy_; in this case, both child_policy_ and
+  //    pending_child_policy_ are non-null).  In this case:
+  //    a. If pending_child_policy_->name() equals child_policy_name,
+  //       then we update the existing pending child policy.
+  //    b. If pending_child_policy->name() does not equal
+  //       child_policy_name, then we create a new policy.  The new
+  //       policy is stored in pending_child_policy_ (replacing the one
+  //       that was there before, which will be immediately shut down)
+  //       and will later be swapped into child_policy_ by the helper
+  //       when the new child transitions into state READY.
+  const char* fallback_policy_name = fallback_policy_config_ == nullptr
+                                         ? "round_robin"
+                                         : fallback_policy_config_->name();
+  const bool create_policy =
+      // case 1
+      fallback_policy_ == nullptr ||
+      // case 2b
+      (pending_fallback_policy_ == nullptr &&
+       strcmp(fallback_policy_->name(), fallback_policy_name) != 0) ||
+      // case 3b
+      (pending_fallback_policy_ != nullptr &&
+       strcmp(pending_fallback_policy_->name(), fallback_policy_name) != 0);
+  LoadBalancingPolicy* policy_to_update = nullptr;
+  if (create_policy) {
+    // Cases 1, 2b, and 3b: create a new child policy.
+    // If child_policy_ is null, we set it (case 1), else we set
+    // pending_child_policy_ (cases 2b and 3b).
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+      gpr_log(GPR_INFO, "[xdslb %p] Creating new %sfallback policy %s", this,
+              fallback_policy_ == nullptr ? "" : "pending ",
+              fallback_policy_name);
+    }
+    auto new_policy =
+        CreateFallbackPolicyLocked(fallback_policy_name, update_args.args);
+    auto& lb_policy = fallback_policy_ == nullptr ? fallback_policy_
+                                                  : pending_fallback_policy_;
+    {
+      MutexLock lock(&fallback_policy_mu_);
+      lb_policy = std::move(new_policy);
+    }
+    policy_to_update = lb_policy.get();
+  } else {
+    // Cases 2a and 3a: update an existing policy.
+    // If we have a pending child policy, send the update to the pending
+    // policy (case 3a), else send it to the current policy (case 2a).
+    policy_to_update = pending_fallback_policy_ != nullptr
+                           ? pending_fallback_policy_.get()
+                           : fallback_policy_.get();
+  }
+  GPR_ASSERT(policy_to_update != nullptr);
+  // Update the policy.
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+    gpr_log(
+        GPR_INFO, "[xdslb %p] Updating %sfallback policy %p", this,
+        policy_to_update == pending_fallback_policy_.get() ? "pending " : "",
+        policy_to_update);
+  }
+  policy_to_update->UpdateLocked(std::move(update_args));
+}
+
+OrphanablePtr<LoadBalancingPolicy> XdsLb::CreateFallbackPolicyLocked(
+    const char* name, const grpc_channel_args* args) {
+  FallbackHelper* helper = New<FallbackHelper>(Ref());
+  LoadBalancingPolicy::Args lb_policy_args;
+  lb_policy_args.combiner = combiner();
+  lb_policy_args.args = args;
+  lb_policy_args.channel_control_helper =
+      UniquePtr<ChannelControlHelper>(helper);
+  OrphanablePtr<LoadBalancingPolicy> lb_policy =
+      LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy(
+          name, std::move(lb_policy_args));
+  if (GPR_UNLIKELY(lb_policy == nullptr)) {
+    gpr_log(GPR_ERROR, "[xdslb %p] Failure creating fallback policy %s", this,
+            name);
+    return nullptr;
+  }
+  helper->set_child(lb_policy.get());
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+    gpr_log(GPR_INFO, "[xdslb %p] Created new fallback policy %s (%p)", this,
+            name, lb_policy.get());
+  }
+  // Add the xDS's interested_parties pollset_set to that of the newly created
+  // child policy. This will make the child policy progress upon activity on xDS
+  // LB, which in turn is tied to the application's call.
+  grpc_pollset_set_add_pollset_set(lb_policy->interested_parties(),
+                                   interested_parties());
+  return lb_policy;
+}
+
+void XdsLb::MaybeExitFallbackMode() {
+  if (fallback_policy_ == nullptr) return;
+  gpr_log(GPR_INFO, "[xdslb %p] Exiting fallback mode", this);
+  fallback_policy_.reset();
+  pending_fallback_policy_.reset();
+}
+
 //
-// code for interacting with the child policy
+// XdsLb::LocalityMap
 //
 
-grpc_channel_args* XdsLb::CreateChildPolicyArgsLocked() {
+void XdsLb::LocalityMap::PruneLocalities(const LocalityList& locality_list) {
+  for (auto iter = map_.begin(); iter != map_.end();) {
+    bool found = false;
+    for (size_t i = 0; i < locality_list.size(); i++) {
+      if (!gpr_stricmp(locality_list[i]->locality_name, iter->first.get())) {
+        found = true;
+      }
+    }
+    if (!found) {  // Remove entries not present in the locality list
+      MutexLock lock(&child_refs_mu_);
+      iter = map_.erase(iter);
+    } else
+      iter++;
+  }
+}
+
+void XdsLb::LocalityMap::UpdateLocked(
+    const LocalityList& locality_serverlist,
+    ParsedLoadBalancingConfig* child_policy_config,
+    const grpc_channel_args* args, XdsLb* parent) {
+  if (parent->shutting_down_) return;
+  for (size_t i = 0; i < locality_serverlist.size(); i++) {
+    UniquePtr<char> locality_name(
+        gpr_strdup(locality_serverlist[i]->locality_name));
+    auto iter = map_.find(locality_name);
+    if (iter == map_.end()) {
+      OrphanablePtr<LocalityEntry> new_entry = MakeOrphanable<LocalityEntry>(
+          parent->Ref(), locality_serverlist[i]->locality_weight);
+      MutexLock lock(&child_refs_mu_);
+      iter = map_.emplace(std::move(locality_name), std::move(new_entry)).first;
+    }
+    // Don't create new child policies if not directed to
+    xds_grpclb_serverlist* serverlist =
+        parent->locality_serverlist_[i]->serverlist;
+    iter->second->UpdateLocked(serverlist, child_policy_config, args);
+  }
+  PruneLocalities(locality_serverlist);
+}
+
+void XdsLb::LocalityMap::ShutdownLocked() {
+  MutexLock lock(&child_refs_mu_);
+  map_.clear();
+}
+
+void XdsLb::LocalityMap::ResetBackoffLocked() {
+  for (auto& p : map_) {
+    p.second->ResetBackoffLocked();
+  }
+}
+
+void XdsLb::LocalityMap::FillChildRefsForChannelz(
+    channelz::ChildRefsList* child_subchannels,
+    channelz::ChildRefsList* child_channels) {
+  MutexLock lock(&child_refs_mu_);
+  for (auto& p : map_) {
+    p.second->FillChildRefsForChannelz(child_subchannels, child_channels);
+  }
+}
+
+//
+// XdsLb::LocalityMap::LocalityEntry
+//
+
+grpc_channel_args*
+XdsLb::LocalityMap::LocalityEntry::CreateChildPolicyArgsLocked(
+    const grpc_channel_args* args_in) {
   const grpc_arg args_to_add[] = {
       // A channel arg indicating if the target is a backend inferred from a
       // grpclb load balancer.
@@ -1368,15 +1807,16 @@
       grpc_channel_arg_integer_create(
           const_cast<char*>(GRPC_ARG_INHIBIT_HEALTH_CHECKING), 1),
   };
-  return grpc_channel_args_copy_and_add(args_, args_to_add,
+  return grpc_channel_args_copy_and_add(args_in, args_to_add,
                                         GPR_ARRAY_SIZE(args_to_add));
 }
 
-OrphanablePtr<LoadBalancingPolicy> XdsLb::CreateChildPolicyLocked(
+OrphanablePtr<LoadBalancingPolicy>
+XdsLb::LocalityMap::LocalityEntry::CreateChildPolicyLocked(
     const char* name, const grpc_channel_args* args) {
-  Helper* helper = New<Helper>(Ref());
+  Helper* helper = New<Helper>(this->Ref());
   LoadBalancingPolicy::Args lb_policy_args;
-  lb_policy_args.combiner = combiner();
+  lb_policy_args.combiner = parent_->combiner();
   lb_policy_args.args = args;
   lb_policy_args.channel_control_helper =
       UniquePtr<ChannelControlHelper>(helper);
@@ -1389,7 +1829,7 @@
     return nullptr;
   }
   helper->set_child(lb_policy.get());
-  if (grpc_lb_xds_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     gpr_log(GPR_INFO, "[xdslb %p] Created new child policy %s (%p)", this, name,
             lb_policy.get());
   }
@@ -1397,22 +1837,21 @@
   // child policy. This will make the child policy progress upon activity on xDS
   // LB, which in turn is tied to the application's call.
   grpc_pollset_set_add_pollset_set(lb_policy->interested_parties(),
-                                   interested_parties());
+                                   parent_->interested_parties());
   return lb_policy;
 }
 
-void XdsLb::CreateOrUpdateChildPolicyLocked() {
-  if (shutting_down_) return;
-  // This should never be invoked if we do not have serverlist_, as fallback
-  // mode is disabled for xDS plugin.
-  // TODO(juanlishen): Change this as part of implementing fallback mode.
-  GPR_ASSERT(serverlist_ != nullptr);
-  GPR_ASSERT(serverlist_->num_servers > 0);
+void XdsLb::LocalityMap::LocalityEntry::UpdateLocked(
+    xds_grpclb_serverlist* serverlist,
+    ParsedLoadBalancingConfig* child_policy_config,
+    const grpc_channel_args* args_in) {
+  if (parent_->shutting_down_) return;
   // Construct update args.
   UpdateArgs update_args;
-  update_args.addresses = ProcessServerlist(serverlist_);
-  update_args.config = child_policy_config_;
-  update_args.args = CreateChildPolicyArgsLocked();
+  update_args.addresses = ProcessServerlist(serverlist);
+  update_args.config =
+      child_policy_config == nullptr ? nullptr : child_policy_config->Ref();
+  update_args.args = CreateChildPolicyArgsLocked(args_in);
   // If the child policy name changes, we need to create a new child
   // policy.  When this happens, we leave child_policy_ as-is and store
   // the new child policy in pending_child_policy_.  Once the new child
@@ -1464,9 +1903,9 @@
   //       when the new child transitions into state READY.
   // TODO(juanlishen): If the child policy is not configured via service config,
   // use whatever algorithm is specified by the balancer.
-  const char* child_policy_name = child_policy_config_ == nullptr
+  const char* child_policy_name = child_policy_config == nullptr
                                       ? "round_robin"
-                                      : child_policy_config_->name();
+                                      : child_policy_config->name();
   const bool create_policy =
       // case 1
       child_policy_ == nullptr ||
@@ -1481,7 +1920,7 @@
     // Cases 1, 2b, and 3b: create a new child policy.
     // If child_policy_ is null, we set it (case 1), else we set
     // pending_child_policy_ (cases 2b and 3b).
-    if (grpc_lb_xds_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
       gpr_log(GPR_INFO, "[xdslb %p] Creating new %schild policy %s", this,
               child_policy_ == nullptr ? "" : "pending ", child_policy_name);
     }
@@ -1504,7 +1943,7 @@
   }
   GPR_ASSERT(policy_to_update != nullptr);
   // Update the policy.
-  if (grpc_lb_xds_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
     gpr_log(GPR_INFO, "[xdslb %p] Updating %schild policy %p", this,
             policy_to_update == pending_child_policy_.get() ? "pending " : "",
             policy_to_update);
@@ -1512,6 +1951,201 @@
   policy_to_update->UpdateLocked(std::move(update_args));
 }
 
+void XdsLb::LocalityMap::LocalityEntry::ShutdownLocked() {
+  // Remove the child policy's interested_parties pollset_set from the
+  // xDS policy.
+  grpc_pollset_set_del_pollset_set(child_policy_->interested_parties(),
+                                   parent_->interested_parties());
+  if (pending_child_policy_ != nullptr) {
+    grpc_pollset_set_del_pollset_set(
+        pending_child_policy_->interested_parties(),
+        parent_->interested_parties());
+  }
+  {
+    MutexLock lock(&child_policy_mu_);
+    child_policy_.reset();
+    pending_child_policy_.reset();
+  }
+}
+
+void XdsLb::LocalityMap::LocalityEntry::ResetBackoffLocked() {
+  child_policy_->ResetBackoffLocked();
+  if (pending_child_policy_ != nullptr) {
+    pending_child_policy_->ResetBackoffLocked();
+  }
+}
+
+void XdsLb::LocalityMap::LocalityEntry::FillChildRefsForChannelz(
+    channelz::ChildRefsList* child_subchannels,
+    channelz::ChildRefsList* child_channels) {
+  MutexLock lock(&child_policy_mu_);
+  child_policy_->FillChildRefsForChannelz(child_subchannels, child_channels);
+  if (pending_child_policy_ != nullptr) {
+    pending_child_policy_->FillChildRefsForChannelz(child_subchannels,
+                                                    child_channels);
+  }
+}
+
+void XdsLb::LocalityMap::LocalityEntry::Orphan() {
+  ShutdownLocked();
+  Unref();
+}
+
+//
+// XdsLb::LocalityEntry::Helper
+//
+
+bool XdsLb::LocalityMap::LocalityEntry::Helper::CalledByPendingChild() const {
+  GPR_ASSERT(child_ != nullptr);
+  return child_ == entry_->pending_child_policy_.get();
+}
+
+bool XdsLb::LocalityMap::LocalityEntry::Helper::CalledByCurrentChild() const {
+  GPR_ASSERT(child_ != nullptr);
+  return child_ == entry_->child_policy_.get();
+}
+
+Subchannel* XdsLb::LocalityMap::LocalityEntry::Helper::CreateSubchannel(
+    const grpc_channel_args& args) {
+  if (entry_->parent_->shutting_down_ ||
+      (!CalledByPendingChild() && !CalledByCurrentChild())) {
+    return nullptr;
+  }
+  return entry_->parent_->channel_control_helper()->CreateSubchannel(args);
+}
+
+grpc_channel* XdsLb::LocalityMap::LocalityEntry::Helper::CreateChannel(
+    const char* target, const grpc_channel_args& args) {
+  if (entry_->parent_->shutting_down_ ||
+      (!CalledByPendingChild() && !CalledByCurrentChild())) {
+    return nullptr;
+  }
+  return entry_->parent_->channel_control_helper()->CreateChannel(target, args);
+}
+
+void XdsLb::LocalityMap::LocalityEntry::Helper::UpdateState(
+    grpc_connectivity_state state, UniquePtr<SubchannelPicker> picker) {
+  if (entry_->parent_->shutting_down_) return;
+  // If this request is from the pending child policy, ignore it until
+  // it reports READY, at which point we swap it into place.
+  if (CalledByPendingChild()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+      gpr_log(GPR_INFO,
+              "[xdslb %p helper %p] pending child policy %p reports state=%s",
+              entry_->parent_.get(), this, entry_->pending_child_policy_.get(),
+              grpc_connectivity_state_name(state));
+    }
+    if (state != GRPC_CHANNEL_READY) return;
+    grpc_pollset_set_del_pollset_set(
+        entry_->child_policy_->interested_parties(),
+        entry_->parent_->interested_parties());
+    MutexLock lock(&entry_->child_policy_mu_);
+    entry_->child_policy_ = std::move(entry_->pending_child_policy_);
+  } else if (!CalledByCurrentChild()) {
+    // This request is from an outdated child, so ignore it.
+    return;
+  }
+  // At this point, child_ must be the current child policy.
+  if (state == GRPC_CHANNEL_READY) entry_->parent_->MaybeExitFallbackMode();
+  // If we are in fallback mode, ignore update request from the child policy.
+  if (entry_->parent_->fallback_policy_ != nullptr) return;
+  GPR_ASSERT(entry_->parent_->lb_chand_ != nullptr);
+  RefCountedPtr<XdsLbClientStats> client_stats =
+      entry_->parent_->lb_chand_->lb_calld() == nullptr
+          ? nullptr
+          : entry_->parent_->lb_chand_->lb_calld()->client_stats();
+  // Cache the picker and its state in the entry
+  entry_->picker_ref_ = MakeRefCounted<PickerRef>(std::move(picker));
+  entry_->connectivity_state_ = state;
+  // Construct a new xds picker which maintains a map of all locality pickers
+  // that are ready. Each locality is represented by a portion of the range
+  // proportional to its weight, such that the total range is the sum of the
+  // weights of all localities
+  uint32_t end = 0;
+  size_t num_connecting = 0;
+  size_t num_idle = 0;
+  size_t num_transient_failures = 0;
+  auto& locality_map = this->entry_->parent_->locality_map_.map_;
+  Picker::PickerList pickers;
+  for (auto& p : locality_map) {
+    const LocalityEntry* entry = p.second.get();
+    grpc_connectivity_state connectivity_state = entry->connectivity_state_;
+    switch (connectivity_state) {
+      case GRPC_CHANNEL_READY: {
+        end += entry->locality_weight_;
+        pickers.push_back(MakePair(end, entry->picker_ref_));
+        break;
+      }
+      case GRPC_CHANNEL_CONNECTING: {
+        num_connecting++;
+        break;
+      }
+      case GRPC_CHANNEL_IDLE: {
+        num_idle++;
+        break;
+      }
+      case GRPC_CHANNEL_TRANSIENT_FAILURE: {
+        num_transient_failures++;
+        break;
+      }
+      default: {
+        gpr_log(GPR_ERROR, "Invalid locality connectivity state - %d",
+                connectivity_state);
+      }
+    }
+  }
+  // Pass on the constructed xds picker if it has any ready pickers in their map
+  // otherwise pass a QueuePicker if any of the locality pickers are in a
+  // connecting or idle state, finally return a transient failure picker if all
+  // locality pickers are in transient failure
+  if (pickers.size() > 0) {
+    entry_->parent_->channel_control_helper()->UpdateState(
+        GRPC_CHANNEL_READY,
+        UniquePtr<LoadBalancingPolicy::SubchannelPicker>(
+            New<Picker>(std::move(client_stats), std::move(pickers))));
+  } else if (num_connecting > 0) {
+    entry_->parent_->channel_control_helper()->UpdateState(
+        GRPC_CHANNEL_CONNECTING,
+        UniquePtr<SubchannelPicker>(New<QueuePicker>(this->entry_->parent_)));
+  } else if (num_idle > 0) {
+    entry_->parent_->channel_control_helper()->UpdateState(
+        GRPC_CHANNEL_IDLE,
+        UniquePtr<SubchannelPicker>(New<QueuePicker>(this->entry_->parent_)));
+  } else {
+    GPR_ASSERT(num_transient_failures == locality_map.size());
+    grpc_error* error =
+        grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                               "connections to all localities failing"),
+                           GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
+    entry_->parent_->channel_control_helper()->UpdateState(
+        state, UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(error)));
+  }
+}
+
+void XdsLb::LocalityMap::LocalityEntry::Helper::RequestReresolution() {
+  if (entry_->parent_->shutting_down_) return;
+  // If there is a pending child policy, ignore re-resolution requests
+  // from the current child policy (or any outdated child).
+  if (entry_->pending_child_policy_ != nullptr && !CalledByPendingChild()) {
+    return;
+  }
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_xds_trace)) {
+    gpr_log(GPR_INFO,
+            "[xdslb %p] Re-resolution requested from the internal RR policy "
+            "(%p).",
+            entry_->parent_.get(), entry_->child_policy_.get());
+  }
+  GPR_ASSERT(entry_->parent_->lb_chand_ != nullptr);
+  // If we are talking to a balancer, we expect to get updated addresses
+  // from the balancer, so we can ignore the re-resolution request from
+  // the child policy. Otherwise, pass the re-resolution request up to the
+  // channel.
+  if (entry_->parent_->lb_chand_->lb_calld() == nullptr ||
+      !entry_->parent_->lb_chand_->lb_calld()->seen_initial_response()) {
+    entry_->parent_->channel_control_helper()->RequestReresolution();
+  }
+}
+
 //
 // factory
 //
@@ -1524,6 +2158,77 @@
   }
 
   const char* name() const override { return kXds; }
+
+  RefCountedPtr<ParsedLoadBalancingConfig> ParseLoadBalancingConfig(
+      const grpc_json* json, grpc_error** error) const override {
+    GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+    if (json == nullptr) {
+      // xds was mentioned as a policy in the deprecated loadBalancingPolicy
+      // field or in the client API.
+      *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "field:loadBalancingPolicy error:Xds Parser has required field - "
+          "balancerName. Please use loadBalancingConfig field of service "
+          "config instead.");
+      return nullptr;
+    }
+    GPR_DEBUG_ASSERT(strcmp(json->key, name()) == 0);
+
+    InlinedVector<grpc_error*, 3> error_list;
+    const char* balancer_name = nullptr;
+    RefCountedPtr<ParsedLoadBalancingConfig> child_policy;
+    RefCountedPtr<ParsedLoadBalancingConfig> fallback_policy;
+    for (const grpc_json* field = json->child; field != nullptr;
+         field = field->next) {
+      if (field->key == nullptr) continue;
+      if (strcmp(field->key, "balancerName") == 0) {
+        if (balancer_name != nullptr) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:balancerName error:Duplicate entry"));
+        }
+        if (field->type != GRPC_JSON_STRING) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:balancerName error:type should be string"));
+          continue;
+        }
+        balancer_name = field->value;
+      } else if (strcmp(field->key, "childPolicy") == 0) {
+        if (child_policy != nullptr) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:childPolicy error:Duplicate entry"));
+        }
+        grpc_error* parse_error = GRPC_ERROR_NONE;
+        child_policy = LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(
+            field, &parse_error);
+        if (child_policy == nullptr) {
+          GPR_DEBUG_ASSERT(parse_error != GRPC_ERROR_NONE);
+          error_list.push_back(parse_error);
+        }
+      } else if (strcmp(field->key, "fallbackPolicy") == 0) {
+        if (fallback_policy != nullptr) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:fallbackPolicy error:Duplicate entry"));
+        }
+        grpc_error* parse_error = GRPC_ERROR_NONE;
+        fallback_policy = LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(
+            field, &parse_error);
+        if (fallback_policy == nullptr) {
+          GPR_DEBUG_ASSERT(parse_error != GRPC_ERROR_NONE);
+          error_list.push_back(parse_error);
+        }
+      }
+    }
+    if (balancer_name == nullptr) {
+      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "field:balancerName error:not found"));
+    }
+    if (error_list.empty()) {
+      return RefCountedPtr<ParsedLoadBalancingConfig>(New<ParsedXdsConfig>(
+          balancer_name, std::move(child_policy), std::move(fallback_policy)));
+    } else {
+      *error = GRPC_ERROR_CREATE_FROM_VECTOR("Xds Parser", &error_list);
+      return nullptr;
+    }
+  }
 };
 
 }  // namespace
diff --git a/src/core/ext/filters/client_channel/lb_policy_factory.h b/src/core/ext/filters/client_channel/lb_policy_factory.h
index 1da4b7c..aaf3e95 100644
--- a/src/core/ext/filters/client_channel/lb_policy_factory.h
+++ b/src/core/ext/filters/client_channel/lb_policy_factory.h
@@ -37,9 +37,12 @@
   /// Caller does NOT take ownership of result.
   virtual const char* name() const GRPC_ABSTRACT;
 
+  virtual RefCountedPtr<ParsedLoadBalancingConfig> ParseLoadBalancingConfig(
+      const grpc_json* json, grpc_error** error) const GRPC_ABSTRACT;
+
   virtual ~LoadBalancingPolicyFactory() {}
 
-  GRPC_ABSTRACT_BASE_CLASS
+  GRPC_ABSTRACT_BASE_CLASS;
 };
 
 }  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/lb_policy_registry.cc b/src/core/ext/filters/client_channel/lb_policy_registry.cc
index 99980d5..973aa26 100644
--- a/src/core/ext/filters/client_channel/lb_policy_registry.cc
+++ b/src/core/ext/filters/client_channel/lb_policy_registry.cc
@@ -94,9 +94,112 @@
   return factory->CreateLoadBalancingPolicy(std::move(args));
 }
 
-bool LoadBalancingPolicyRegistry::LoadBalancingPolicyExists(const char* name) {
+bool LoadBalancingPolicyRegistry::LoadBalancingPolicyExists(
+    const char* name, bool* requires_config) {
   GPR_ASSERT(g_state != nullptr);
-  return g_state->GetLoadBalancingPolicyFactory(name) != nullptr;
+  auto* factory = g_state->GetLoadBalancingPolicyFactory(name);
+  if (factory == nullptr) {
+    return false;
+  }
+  if (requires_config != nullptr) {
+    grpc_error* error = GRPC_ERROR_NONE;
+    // Check if the load balancing policy allows an empty config
+    *requires_config =
+        factory->ParseLoadBalancingConfig(nullptr, &error) == nullptr;
+    GRPC_ERROR_UNREF(error);
+  }
+  return true;
+}
+
+namespace {
+// Returns the JSON node of policy (with both policy name and config content)
+// given the JSON node of a LoadBalancingConfig array.
+grpc_json* ParseLoadBalancingConfigHelper(const grpc_json* lb_config_array,
+                                          grpc_error** error) {
+  GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+  char* error_msg;
+  if (lb_config_array == nullptr || lb_config_array->type != GRPC_JSON_ARRAY) {
+    gpr_asprintf(&error_msg, "field:%s error:type should be array",
+                 lb_config_array->key);
+    *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
+    gpr_free(error_msg);
+    return nullptr;
+  }
+  const char* field_name = lb_config_array->key;
+  // Find the first LB policy that this client supports.
+  for (const grpc_json* lb_config = lb_config_array->child;
+       lb_config != nullptr; lb_config = lb_config->next) {
+    if (lb_config->type != GRPC_JSON_OBJECT) {
+      gpr_asprintf(&error_msg,
+                   "field:%s error:child entry should be of type object",
+                   field_name);
+      *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
+      gpr_free(error_msg);
+      return nullptr;
+    }
+    grpc_json* policy = nullptr;
+    for (grpc_json* field = lb_config->child; field != nullptr;
+         field = field->next) {
+      if (field->key == nullptr || field->type != GRPC_JSON_OBJECT) {
+        gpr_asprintf(&error_msg,
+                     "field:%s error:child entry should be of type object",
+                     field_name);
+        *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
+        gpr_free(error_msg);
+        return nullptr;
+      }
+      if (policy != nullptr) {
+        gpr_asprintf(&error_msg, "field:%s error:oneOf violation", field_name);
+        *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
+        gpr_free(error_msg);
+        return nullptr;
+      }  // Violate "oneof" type.
+      policy = field;
+    }
+    if (policy == nullptr) {
+      gpr_asprintf(&error_msg, "field:%s error:no policy found in child entry",
+                   field_name);
+      *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
+      gpr_free(error_msg);
+      return nullptr;
+    }
+    // If we support this policy, then select it.
+    if (LoadBalancingPolicyRegistry::LoadBalancingPolicyExists(policy->key,
+                                                               nullptr)) {
+      return policy;
+    }
+  }
+  gpr_asprintf(&error_msg, "field:%s error:No known policy", field_name);
+  *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
+  gpr_free(error_msg);
+  return nullptr;
+}
+}  // namespace
+
+RefCountedPtr<ParsedLoadBalancingConfig>
+LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(const grpc_json* json,
+                                                      grpc_error** error) {
+  GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+  GPR_ASSERT(g_state != nullptr);
+  const grpc_json* policy = ParseLoadBalancingConfigHelper(json, error);
+  if (policy == nullptr) {
+    return nullptr;
+  } else {
+    GPR_DEBUG_ASSERT(*error == GRPC_ERROR_NONE && json != nullptr);
+    // Find factory.
+    LoadBalancingPolicyFactory* factory =
+        g_state->GetLoadBalancingPolicyFactory(policy->key);
+    if (factory == nullptr) {
+      char* msg;
+      gpr_asprintf(&msg, "field:%s error:Factory not found to create policy",
+                   json->key);
+      *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
+      gpr_free(msg);
+      return nullptr;
+    }
+    // Parse load balancing config via factory.
+    return factory->ParseLoadBalancingConfig(policy, error);
+  }
 }
 
 }  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/lb_policy_registry.h b/src/core/ext/filters/client_channel/lb_policy_registry.h
index 7472ba9..6820cfc 100644
--- a/src/core/ext/filters/client_channel/lb_policy_registry.h
+++ b/src/core/ext/filters/client_channel/lb_policy_registry.h
@@ -49,8 +49,15 @@
       const char* name, LoadBalancingPolicy::Args args);
 
   /// Returns true if the LB policy factory specified by \a name exists in this
-  /// registry.
-  static bool LoadBalancingPolicyExists(const char* name);
+  /// registry. If the load balancing policy requires a config to be specified
+  /// then sets \a requires_config to true.
+  static bool LoadBalancingPolicyExists(const char* name,
+                                        bool* requires_config);
+
+  /// Returns a parsed object of the load balancing policy to be used from a
+  /// LoadBalancingConfig array \a json.
+  static RefCountedPtr<ParsedLoadBalancingConfig> ParseLoadBalancingConfig(
+      const grpc_json* json, grpc_error** error);
 };
 
 }  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
index 13fde4a..0035873 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
@@ -18,7 +18,7 @@
 
 #include <grpc/support/port_platform.h>
 
-#if GRPC_ARES == 1 && !defined(GRPC_UV)
+#if GRPC_ARES == 1
 
 #include <limits.h>
 #include <stdio.h>
@@ -32,12 +32,12 @@
 #include "src/core/ext/filters/client_channel/http_connect_handshaker.h"
 #include "src/core/ext/filters/client_channel/lb_policy_registry.h"
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/ext/filters/client_channel/service_config.h"
 #include "src/core/lib/backoff/backoff.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
@@ -227,65 +227,94 @@
   return false;
 }
 
-char* ChooseServiceConfig(char* service_config_choice_json) {
+char* ChooseServiceConfig(char* service_config_choice_json,
+                          grpc_error** error) {
   grpc_json* choices_json = grpc_json_parse_string(service_config_choice_json);
-  if (choices_json == nullptr || choices_json->type != GRPC_JSON_ARRAY) {
-    gpr_log(GPR_ERROR, "cannot parse service config JSON string");
+  if (choices_json == nullptr) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "Service Config JSON Parsing, error: could not parse");
+    return nullptr;
+  }
+  if (choices_json->type != GRPC_JSON_ARRAY) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "Service Config Choices, error: should be of type array");
     return nullptr;
   }
   char* service_config = nullptr;
+  InlinedVector<grpc_error*, 4> error_list;
+  bool found_choice = false;  // have we found a choice?
   for (grpc_json* choice = choices_json->child; choice != nullptr;
        choice = choice->next) {
     if (choice->type != GRPC_JSON_OBJECT) {
-      gpr_log(GPR_ERROR, "cannot parse service config JSON string");
-      break;
+      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "Service Config Choice, error: should be of type object"));
+      continue;
     }
     grpc_json* service_config_json = nullptr;
+    bool selected = true;  // has this choice been rejected?
     for (grpc_json* field = choice->child; field != nullptr;
          field = field->next) {
       // Check client language, if specified.
       if (strcmp(field->key, "clientLanguage") == 0) {
-        if (field->type != GRPC_JSON_ARRAY || !ValueInJsonArray(field, "c++")) {
-          service_config_json = nullptr;
-          break;
+        if (field->type != GRPC_JSON_ARRAY) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:clientLanguage error:should be of type array"));
+        } else if (!ValueInJsonArray(field, "c++")) {
+          selected = false;
         }
       }
       // Check client hostname, if specified.
       if (strcmp(field->key, "clientHostname") == 0) {
+        if (field->type != GRPC_JSON_ARRAY) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:clientHostname error:should be of type array"));
+          continue;
+        }
         char* hostname = grpc_gethostname();
-        if (hostname == nullptr || field->type != GRPC_JSON_ARRAY ||
-            !ValueInJsonArray(field, hostname)) {
-          service_config_json = nullptr;
-          break;
+        if (hostname == nullptr || !ValueInJsonArray(field, hostname)) {
+          selected = false;
         }
       }
       // Check percentage, if specified.
       if (strcmp(field->key, "percentage") == 0) {
         if (field->type != GRPC_JSON_NUMBER) {
-          service_config_json = nullptr;
-          break;
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:percentage error:should be of type number"));
+          continue;
         }
         int random_pct = rand() % 100;
         int percentage;
-        if (sscanf(field->value, "%d", &percentage) != 1 ||
-            random_pct > percentage || percentage == 0) {
-          service_config_json = nullptr;
-          break;
+        if (sscanf(field->value, "%d", &percentage) != 1) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:percentage error:should be of type integer"));
+          continue;
+        }
+        if (random_pct > percentage || percentage == 0) {
+          selected = false;
         }
       }
       // Save service config.
       if (strcmp(field->key, "serviceConfig") == 0) {
         if (field->type == GRPC_JSON_OBJECT) {
           service_config_json = field;
+        } else {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:serviceConfig error:should be of type object"));
         }
       }
     }
-    if (service_config_json != nullptr) {
+    if (!found_choice && selected && service_config_json != nullptr) {
       service_config = grpc_json_dump_to_string(service_config_json, 0);
-      break;
+      found_choice = true;
     }
   }
   grpc_json_destroy(choices_json);
+  if (!error_list.empty()) {
+    gpr_free(service_config);
+    service_config = nullptr;
+    *error = GRPC_ERROR_CREATE_FROM_VECTOR("Service Config Choices Parser",
+                                           &error_list);
+  }
   return service_config;
 }
 
@@ -303,13 +332,15 @@
     Result result;
     result.addresses = std::move(*r->addresses_);
     if (r->service_config_json_ != nullptr) {
-      char* service_config_string =
-          ChooseServiceConfig(r->service_config_json_);
+      char* service_config_string = ChooseServiceConfig(
+          r->service_config_json_, &result.service_config_error);
       gpr_free(r->service_config_json_);
-      if (service_config_string != nullptr) {
+      if (result.service_config_error == GRPC_ERROR_NONE &&
+          service_config_string != nullptr) {
         GRPC_CARES_TRACE_LOG("resolver:%p selected service config choice: %s",
                              r, service_config_string);
-        result.service_config = ServiceConfig::Create(service_config_string);
+        result.service_config = ServiceConfig::Create(
+            service_config_string, &result.service_config_error);
       }
       gpr_free(service_config_string);
     }
@@ -426,6 +457,13 @@
 static grpc_address_resolver_vtable ares_resolver = {
     grpc_resolve_address_ares, blocking_resolve_address_ares};
 
+#ifdef GRPC_UV
+/* TODO(murgatroid99): Remove this when we want the cares resolver to be the
+ * default when using libuv */
+static bool should_use_ares(const char* resolver_env) {
+  return resolver_env != nullptr && gpr_stricmp(resolver_env, "ares") == 0;
+}
+#else  /* GRPC_UV */
 static bool should_use_ares(const char* resolver_env) {
   // TODO(lidiz): Remove the "g_custom_iomgr_enabled" flag once c-ares support
   // custom IO managers (e.g. gevent).
@@ -433,10 +471,12 @@
          (resolver_env == nullptr || strlen(resolver_env) == 0 ||
           gpr_stricmp(resolver_env, "ares") == 0);
 }
+#endif /* GRPC_UV */
 
 void grpc_resolver_dns_ares_init() {
-  char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (should_use_ares(resolver_env)) {
+  grpc_core::UniquePtr<char> resolver =
+      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+  if (should_use_ares(resolver.get())) {
     gpr_log(GPR_DEBUG, "Using ares dns resolver");
     address_sorting_init();
     grpc_error* error = grpc_ares_init();
@@ -452,22 +492,21 @@
         grpc_core::UniquePtr<grpc_core::ResolverFactory>(
             grpc_core::New<grpc_core::AresDnsResolverFactory>()));
   }
-  gpr_free(resolver_env);
 }
 
 void grpc_resolver_dns_ares_shutdown() {
-  char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (should_use_ares(resolver_env)) {
+  grpc_core::UniquePtr<char> resolver =
+      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+  if (should_use_ares(resolver.get())) {
     address_sorting_shutdown();
     grpc_ares_cleanup();
   }
-  gpr_free(resolver_env);
 }
 
-#else /* GRPC_ARES == 1 && !defined(GRPC_UV) */
+#else /* GRPC_ARES == 1 */
 
 void grpc_resolver_dns_ares_init(void) {}
 
 void grpc_resolver_dns_ares_shutdown(void) {}
 
-#endif /* GRPC_ARES == 1 && !defined(GRPC_UV) */
+#endif /* GRPC_ARES == 1 */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc
index d99c2e3..4ad8078 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc
@@ -18,7 +18,7 @@
 #include <grpc/support/port_platform.h>
 
 #include "src/core/lib/iomgr/port.h"
-#if GRPC_ARES == 1 && !defined(GRPC_UV)
+#if GRPC_ARES == 1
 
 #include <ares.h>
 #include <string.h>
@@ -84,6 +84,10 @@
   grpc_timer query_timeout;
   /** cancels queries on a timeout */
   grpc_closure on_timeout_locked;
+  /** alarm to poll ares_process on in case fd events don't happen */
+  grpc_timer ares_backup_poll_alarm;
+  /** polls ares_process on a periodic timer */
+  grpc_closure on_ares_backup_poll_alarm_locked;
 };
 
 static void grpc_ares_notify_on_event_locked(grpc_ares_ev_driver* ev_driver);
@@ -130,6 +134,13 @@
 
 static void on_timeout_locked(void* arg, grpc_error* error);
 
+static void on_ares_backup_poll_alarm_locked(void* arg, grpc_error* error);
+
+static void noop_inject_channel_config(ares_channel channel) {}
+
+void (*grpc_ares_test_only_inject_config)(ares_channel channel) =
+    noop_inject_channel_config;
+
 grpc_error* grpc_ares_ev_driver_create_locked(grpc_ares_ev_driver** ev_driver,
                                               grpc_pollset_set* pollset_set,
                                               int query_timeout_ms,
@@ -140,6 +151,7 @@
   memset(&opts, 0, sizeof(opts));
   opts.flags |= ARES_FLAG_STAYOPEN;
   int status = ares_init_options(&(*ev_driver)->channel, &opts, ARES_OPT_FLAGS);
+  grpc_ares_test_only_inject_config((*ev_driver)->channel);
   GRPC_CARES_TRACE_LOG("request:%p grpc_ares_ev_driver_create_locked", request);
   if (status != ARES_SUCCESS) {
     char* err_msg;
@@ -163,6 +175,9 @@
       ->polled_fd_factory->ConfigureAresChannelLocked((*ev_driver)->channel);
   GRPC_CLOSURE_INIT(&(*ev_driver)->on_timeout_locked, on_timeout_locked,
                     *ev_driver, grpc_combiner_scheduler(combiner));
+  GRPC_CLOSURE_INIT(&(*ev_driver)->on_ares_backup_poll_alarm_locked,
+                    on_ares_backup_poll_alarm_locked, *ev_driver,
+                    grpc_combiner_scheduler(combiner));
   (*ev_driver)->query_timeout_ms = query_timeout_ms;
   return GRPC_ERROR_NONE;
 }
@@ -174,6 +189,7 @@
   // fds; if it's not working, there are no fds to shut down.
   ev_driver->shutting_down = true;
   grpc_timer_cancel(&ev_driver->query_timeout);
+  grpc_timer_cancel(&ev_driver->ares_backup_poll_alarm);
   grpc_ares_ev_driver_unref(ev_driver);
 }
 
@@ -204,6 +220,21 @@
   return nullptr;
 }
 
+static grpc_millis calculate_next_ares_backup_poll_alarm_ms(
+    grpc_ares_ev_driver* driver) {
+  // An alternative here could be to use ares_timeout to try to be more
+  // accurate, but that would require using "struct timeval"'s, which just makes
+  // things a bit more complicated. So just poll every second, as suggested
+  // by the c-ares code comments.
+  grpc_millis ms_until_next_ares_backup_poll_alarm = 1000;
+  GRPC_CARES_TRACE_LOG(
+      "request:%p ev_driver=%p. next ares process poll time in "
+      "%" PRId64 " ms",
+      driver->request, driver, ms_until_next_ares_backup_poll_alarm);
+  return ms_until_next_ares_backup_poll_alarm +
+         grpc_core::ExecCtx::Get()->Now();
+}
+
 static void on_timeout_locked(void* arg, grpc_error* error) {
   grpc_ares_ev_driver* driver = static_cast<grpc_ares_ev_driver*>(arg);
   GRPC_CARES_TRACE_LOG(
@@ -216,8 +247,50 @@
   grpc_ares_ev_driver_unref(driver);
 }
 
+/* In case of non-responsive DNS servers, dropped packets, etc., c-ares has
+ * intelligent timeout and retry logic, which we can take advantage of by
+ * polling ares_process_fd on time intervals. Overall, the c-ares library is
+ * meant to be called into and given a chance to proceed name resolution:
+ *   a) when fd events happen
+ *   b) when some time has passed without fd events having happened
+ * For the latter, we use this backup poller. Also see
+ * https://github.com/grpc/grpc/pull/17688 description for more details. */
+static void on_ares_backup_poll_alarm_locked(void* arg, grpc_error* error) {
+  grpc_ares_ev_driver* driver = static_cast<grpc_ares_ev_driver*>(arg);
+  GRPC_CARES_TRACE_LOG(
+      "request:%p ev_driver=%p on_ares_backup_poll_alarm_locked. "
+      "driver->shutting_down=%d. "
+      "err=%s",
+      driver->request, driver, driver->shutting_down, grpc_error_string(error));
+  if (!driver->shutting_down && error == GRPC_ERROR_NONE) {
+    fd_node* fdn = driver->fds;
+    while (fdn != nullptr) {
+      if (!fdn->already_shutdown) {
+        GRPC_CARES_TRACE_LOG(
+            "request:%p ev_driver=%p on_ares_backup_poll_alarm_locked; "
+            "ares_process_fd. fd=%s",
+            driver->request, driver, fdn->grpc_polled_fd->GetName());
+        ares_socket_t as = fdn->grpc_polled_fd->GetWrappedAresSocketLocked();
+        ares_process_fd(driver->channel, as, as);
+      }
+      fdn = fdn->next;
+    }
+    if (!driver->shutting_down) {
+      grpc_millis next_ares_backup_poll_alarm =
+          calculate_next_ares_backup_poll_alarm_ms(driver);
+      grpc_ares_ev_driver_ref(driver);
+      grpc_timer_init(&driver->ares_backup_poll_alarm,
+                      next_ares_backup_poll_alarm,
+                      &driver->on_ares_backup_poll_alarm_locked);
+    }
+    grpc_ares_notify_on_event_locked(driver);
+  }
+  grpc_ares_ev_driver_unref(driver);
+}
+
 static void on_readable_locked(void* arg, grpc_error* error) {
   fd_node* fdn = static_cast<fd_node*>(arg);
+  GPR_ASSERT(fdn->readable_registered);
   grpc_ares_ev_driver* ev_driver = fdn->ev_driver;
   const ares_socket_t as = fdn->grpc_polled_fd->GetWrappedAresSocketLocked();
   fdn->readable_registered = false;
@@ -242,6 +315,7 @@
 
 static void on_writable_locked(void* arg, grpc_error* error) {
   fd_node* fdn = static_cast<fd_node*>(arg);
+  GPR_ASSERT(fdn->writable_registered);
   grpc_ares_ev_driver* ev_driver = fdn->ev_driver;
   const ares_socket_t as = fdn->grpc_polled_fd->GetWrappedAresSocketLocked();
   fdn->writable_registered = false;
@@ -351,6 +425,7 @@
   if (!ev_driver->working) {
     ev_driver->working = true;
     grpc_ares_notify_on_event_locked(ev_driver);
+    // Initialize overall DNS resolution timeout alarm
     grpc_millis timeout =
         ev_driver->query_timeout_ms == 0
             ? GRPC_MILLIS_INF_FUTURE
@@ -362,7 +437,14 @@
     grpc_ares_ev_driver_ref(ev_driver);
     grpc_timer_init(&ev_driver->query_timeout, timeout,
                     &ev_driver->on_timeout_locked);
+    // Initialize the backup poll alarm
+    grpc_millis next_ares_backup_poll_alarm =
+        calculate_next_ares_backup_poll_alarm_ms(ev_driver);
+    grpc_ares_ev_driver_ref(ev_driver);
+    grpc_timer_init(&ev_driver->ares_backup_poll_alarm,
+                    next_ares_backup_poll_alarm,
+                    &ev_driver->on_ares_backup_poll_alarm_locked);
   }
 }
 
-#endif /* GRPC_ARES == 1 && !defined(GRPC_UV) */
+#endif /* GRPC_ARES == 1 */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h
index b8cefd9..2d172eb 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h
@@ -54,6 +54,9 @@
 /* Shutdown all the grpc_fds used by \a ev_driver */
 void grpc_ares_ev_driver_shutdown_locked(grpc_ares_ev_driver* ev_driver);
 
+/* Exposed in this header for C-core tests only */
+extern void (*grpc_ares_test_only_inject_config)(ares_channel channel);
+
 namespace grpc_core {
 
 /* A wrapped fd that integrates with the grpc iomgr of the current platform.
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc
new file mode 100644
index 0000000..04e36fb
--- /dev/null
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc
@@ -0,0 +1,179 @@
+/*
+ *
+ * Copyright 2019 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 <grpc/support/port_platform.h>
+
+#include "src/core/lib/iomgr/port.h"
+#if GRPC_ARES == 1 && defined(GRPC_UV)
+
+#include <ares.h>
+#include <uv.h>
+
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+#include <grpc/support/time.h>
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+#include "src/core/lib/gpr/string.h"
+#include "src/core/lib/iomgr/combiner.h"
+
+namespace grpc_core {
+
+void ares_uv_poll_cb(uv_poll_t* handle, int status, int events);
+
+void ares_uv_poll_close_cb(uv_handle_t* handle) { Delete(handle); }
+
+class GrpcPolledFdLibuv : public GrpcPolledFd {
+ public:
+  GrpcPolledFdLibuv(ares_socket_t as, grpc_combiner* combiner)
+      : as_(as), combiner_(combiner) {
+    gpr_asprintf(&name_, "c-ares socket: %" PRIdPTR, (intptr_t)as);
+    handle_ = New<uv_poll_t>();
+    uv_poll_init_socket(uv_default_loop(), handle_, as);
+    handle_->data = this;
+    GRPC_COMBINER_REF(combiner_, "libuv ares event driver");
+  }
+
+  ~GrpcPolledFdLibuv() {
+    gpr_free(name_);
+    GRPC_COMBINER_UNREF(combiner_, "libuv ares event driver");
+  }
+
+  void RegisterForOnReadableLocked(grpc_closure* read_closure) override {
+    GPR_ASSERT(read_closure_ == nullptr);
+    GPR_ASSERT((poll_events_ & UV_READABLE) == 0);
+    read_closure_ = read_closure;
+    poll_events_ |= UV_READABLE;
+    uv_poll_start(handle_, poll_events_, ares_uv_poll_cb);
+  }
+
+  void RegisterForOnWriteableLocked(grpc_closure* write_closure) override {
+    GPR_ASSERT(write_closure_ == nullptr);
+    GPR_ASSERT((poll_events_ & UV_WRITABLE) == 0);
+    write_closure_ = write_closure;
+    poll_events_ |= UV_WRITABLE;
+    uv_poll_start(handle_, poll_events_, ares_uv_poll_cb);
+  }
+
+  bool IsFdStillReadableLocked() override {
+    /* uv_poll_t is based on poll, which is level triggered. So, if cares
+     * leaves some data unread, the event will trigger again. */
+    return false;
+  }
+
+  void ShutdownInternalLocked(grpc_error* error) {
+    uv_poll_stop(handle_);
+    uv_close(reinterpret_cast<uv_handle_t*>(handle_), ares_uv_poll_close_cb);
+    if (read_closure_ != nullptr) {
+      GRPC_CLOSURE_SCHED(read_closure_, GRPC_ERROR_CANCELLED);
+    }
+    if (write_closure_ != nullptr) {
+      GRPC_CLOSURE_SCHED(write_closure_, GRPC_ERROR_CANCELLED);
+    }
+  }
+
+  void ShutdownLocked(grpc_error* error) override {
+    if (grpc_core::ExecCtx::Get() == nullptr) {
+      grpc_core::ExecCtx exec_ctx;
+      ShutdownInternalLocked(error);
+    } else {
+      ShutdownInternalLocked(error);
+    }
+  }
+
+  ares_socket_t GetWrappedAresSocketLocked() override { return as_; }
+
+  const char* GetName() override { return name_; }
+
+  char* name_;
+  ares_socket_t as_;
+  uv_poll_t* handle_;
+  grpc_closure* read_closure_ = nullptr;
+  grpc_closure* write_closure_ = nullptr;
+  int poll_events_ = 0;
+  grpc_combiner* combiner_;
+};
+
+struct AresUvPollCbArg {
+  AresUvPollCbArg(uv_poll_t* handle, int status, int events)
+      : handle(handle), status(status), events(events) {}
+
+  uv_poll_t* handle;
+  int status;
+  int events;
+};
+
+static void ares_uv_poll_cb_locked(void* arg, grpc_error* error) {
+  grpc_core::UniquePtr<AresUvPollCbArg> arg_struct(
+      reinterpret_cast<AresUvPollCbArg*>(arg));
+  uv_poll_t* handle = arg_struct->handle;
+  int status = arg_struct->status;
+  int events = arg_struct->events;
+  GrpcPolledFdLibuv* polled_fd =
+      reinterpret_cast<GrpcPolledFdLibuv*>(handle->data);
+  if (status < 0) {
+    error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("cares polling error");
+    error =
+        grpc_error_set_str(error, GRPC_ERROR_STR_OS_ERROR,
+                           grpc_slice_from_static_string(uv_strerror(status)));
+  }
+  if (events & UV_READABLE) {
+    GPR_ASSERT(polled_fd->read_closure_ != nullptr);
+    GRPC_CLOSURE_SCHED(polled_fd->read_closure_, error);
+    polled_fd->read_closure_ = nullptr;
+    polled_fd->poll_events_ &= ~UV_READABLE;
+  }
+  if (events & UV_WRITABLE) {
+    GPR_ASSERT(polled_fd->write_closure_ != nullptr);
+    GRPC_CLOSURE_SCHED(polled_fd->write_closure_, error);
+    polled_fd->write_closure_ = nullptr;
+    polled_fd->poll_events_ &= ~UV_WRITABLE;
+  }
+  uv_poll_start(handle, polled_fd->poll_events_, ares_uv_poll_cb);
+}
+
+void ares_uv_poll_cb(uv_poll_t* handle, int status, int events) {
+  grpc_core::ExecCtx exec_ctx;
+  GrpcPolledFdLibuv* polled_fd =
+      reinterpret_cast<GrpcPolledFdLibuv*>(handle->data);
+  AresUvPollCbArg* arg = New<AresUvPollCbArg>(handle, status, events);
+  GRPC_CLOSURE_SCHED(
+      GRPC_CLOSURE_CREATE(ares_uv_poll_cb_locked, arg,
+                          grpc_combiner_scheduler(polled_fd->combiner_)),
+      GRPC_ERROR_NONE);
+}
+
+class GrpcPolledFdFactoryLibuv : public GrpcPolledFdFactory {
+ public:
+  GrpcPolledFd* NewGrpcPolledFdLocked(ares_socket_t as,
+                                      grpc_pollset_set* driver_pollset_set,
+                                      grpc_combiner* combiner) override {
+    return New<GrpcPolledFdLibuv>(as, combiner);
+  }
+
+  void ConfigureAresChannelLocked(ares_channel channel) override {}
+};
+
+UniquePtr<GrpcPolledFdFactory> NewGrpcPolledFdFactory(grpc_combiner* combiner) {
+  return UniquePtr<GrpcPolledFdFactory>(New<GrpcPolledFdFactoryLibuv>());
+}
+
+}  // namespace grpc_core
+
+#endif /* GRPC_ARES == 1 && defined(GRPC_UV) */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc
index 9570e32..ce39007 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc
@@ -109,6 +109,7 @@
     read_closure_ = read_closure;
     GPR_ASSERT(GRPC_SLICE_LENGTH(read_buf_) == 0);
     grpc_slice_unref_internal(read_buf_);
+    GPR_ASSERT(!read_buf_has_data_);
     read_buf_ = GRPC_SLICE_MALLOC(4192);
     WSABUF buffer;
     buffer.buf = (char*)GRPC_SLICE_START_PTR(read_buf_);
@@ -175,7 +176,7 @@
     GRPC_CARES_TRACE_LOG(
         "RecvFrom called on fd:|%s|. Current read buf length:|%d|", GetName(),
         GRPC_SLICE_LENGTH(read_buf_));
-    if (GRPC_SLICE_LENGTH(read_buf_) == 0) {
+    if (!read_buf_has_data_) {
       WSASetLastError(WSAEWOULDBLOCK);
       return -1;
     }
@@ -186,6 +187,9 @@
     }
     read_buf_ = grpc_slice_sub_no_ref(read_buf_, bytes_read,
                                       GRPC_SLICE_LENGTH(read_buf_));
+    if (GRPC_SLICE_LENGTH(read_buf_) == 0) {
+      read_buf_has_data_ = false;
+    }
     /* c-ares overloads this recv_from virtual socket function to receive
      * data on both UDP and TCP sockets, and from is nullptr for TCP. */
     if (from != nullptr) {
@@ -302,6 +306,11 @@
     polled_fd->OnIocpReadableInner(error);
   }
 
+  // TODO(apolcyn): improve this error handling to be less conversative.
+  // An e.g. ECONNRESET error here should result in errors when
+  // c-ares reads from this socket later, but it shouldn't necessarily cancel
+  // the entire resolution attempt. Doing so will allow the "inject broken
+  // nameserver list" test to pass on Windows.
   void OnIocpReadableInner(grpc_error* error) {
     if (error == GRPC_ERROR_NONE) {
       if (winsocket_->read_info.wsa_error != 0) {
@@ -323,6 +332,7 @@
     if (error == GRPC_ERROR_NONE) {
       read_buf_ = grpc_slice_sub_no_ref(read_buf_, 0,
                                         winsocket_->read_info.bytes_transfered);
+      read_buf_has_data_ = true;
     } else {
       grpc_slice_unref_internal(read_buf_);
       read_buf_ = grpc_empty_slice();
@@ -370,6 +380,7 @@
   char recv_from_source_addr_[200];
   ares_socklen_t recv_from_source_addr_len_;
   grpc_slice read_buf_;
+  bool read_buf_has_data_ = false;
   grpc_slice write_buf_;
   grpc_closure* read_closure_ = nullptr;
   grpc_closure* write_closure_ = nullptr;
@@ -445,7 +456,8 @@
    */
   static ares_socket_t Socket(int af, int type, int protocol, void* user_data) {
     SockToPolledFdMap* map = static_cast<SockToPolledFdMap*>(user_data);
-    SOCKET s = WSASocket(af, type, protocol, nullptr, 0, WSA_FLAG_OVERLAPPED);
+    SOCKET s = WSASocket(af, type, protocol, nullptr, 0,
+                         grpc_get_default_wsa_socket_flags());
     if (s == INVALID_SOCKET) {
       return s;
     }
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc
index 37b0b36..0f04f14 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc
@@ -18,7 +18,7 @@
 
 #include <grpc/support/port_platform.h>
 
-#if GRPC_ARES == 1 && !defined(GRPC_UV)
+#if GRPC_ARES == 1
 
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 #include "src/core/lib/iomgr/sockaddr.h"
@@ -101,7 +101,7 @@
 }
 
 void grpc_cares_wrapper_address_sorting_sort(ServerAddressList* addresses) {
-  if (grpc_trace_cares_address_sorting.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_cares_address_sorting)) {
     log_address_sorting_list(*addresses, "input");
   }
   address_sorting_sortable* sortables = (address_sorting_sortable*)gpr_zalloc(
@@ -120,7 +120,7 @@
   }
   gpr_free(sortables);
   *addresses = std::move(sorted);
-  if (grpc_trace_cares_address_sorting.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_cares_address_sorting)) {
     log_address_sorting_list(*addresses, "output");
   }
 }
@@ -154,6 +154,10 @@
 static grpc_ares_hostbyname_request* create_hostbyname_request_locked(
     grpc_ares_request* parent_request, char* host, uint16_t port,
     bool is_balancer) {
+  GRPC_CARES_TRACE_LOG(
+      "request:%p create_hostbyname_request_locked host:%s port:%d "
+      "is_balancer:%d",
+      parent_request, host, port, is_balancer);
   grpc_ares_hostbyname_request* hr = static_cast<grpc_ares_hostbyname_request*>(
       gpr_zalloc(sizeof(grpc_ares_hostbyname_request)));
   hr->parent_request = parent_request;
@@ -251,6 +255,8 @@
     GRPC_CARES_TRACE_LOG("request:%p on_srv_query_done_locked ARES_SUCCESS", r);
     struct ares_srv_reply* reply;
     const int parse_status = ares_parse_srv_reply(abuf, alen, &reply);
+    GRPC_CARES_TRACE_LOG("request:%p ares_parse_srv_reply: %d", r,
+                         parse_status);
     if (parse_status == ARES_SUCCESS) {
       ares_channel* channel =
           grpc_ares_ev_driver_get_channel_locked(r->ev_driver);
@@ -687,4 +693,4 @@
     grpc_pollset_set* interested_parties, grpc_closure* on_done,
     grpc_resolved_addresses** addrs) = grpc_resolve_address_ares_impl;
 
-#endif /* GRPC_ARES == 1 && !defined(GRPC_UV) */
+#endif /* GRPC_ARES == 1 */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h
index 2808250..2cb7c9e 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h
@@ -26,16 +26,18 @@
 #include "src/core/lib/iomgr/polling_entity.h"
 #include "src/core/lib/iomgr/resolve_address.h"
 
-#define GRPC_DNS_ARES_DEFAULT_QUERY_TIMEOUT_MS 10000
+#define GRPC_DNS_ARES_DEFAULT_QUERY_TIMEOUT_MS 120000
 
 extern grpc_core::TraceFlag grpc_trace_cares_address_sorting;
 
 extern grpc_core::TraceFlag grpc_trace_cares_resolver;
 
-#define GRPC_CARES_TRACE_LOG(format, ...)                         \
-  if (grpc_trace_cares_resolver.enabled()) {                      \
-    gpr_log(GPR_DEBUG, "(c-ares resolver) " format, __VA_ARGS__); \
-  }
+#define GRPC_CARES_TRACE_LOG(format, ...)                           \
+  do {                                                              \
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_cares_resolver)) {       \
+      gpr_log(GPR_DEBUG, "(c-ares resolver) " format, __VA_ARGS__); \
+    }                                                               \
+  } while (0)
 
 typedef struct grpc_ares_request grpc_ares_request;
 
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc
index 1f4701c..d2de88e 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc
@@ -18,7 +18,7 @@
 
 #include <grpc/support/port_platform.h>
 
-#if GRPC_ARES != 1 || defined(GRPC_UV)
+#if GRPC_ARES != 1
 
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 
@@ -62,4 +62,4 @@
     grpc_pollset_set* interested_parties, grpc_closure* on_done,
     grpc_resolved_addresses** addrs) = grpc_resolve_address_ares_impl;
 
-#endif /* GRPC_ARES != 1 || defined(GRPC_UV) */
+#endif /* GRPC_ARES != 1 */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc
new file mode 100644
index 0000000..fdbb896
--- /dev/null
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright 2016 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 <grpc/support/port_platform.h>
+
+#include "src/core/lib/iomgr/port.h"
+#if GRPC_ARES == 1 && defined(GRPC_UV)
+
+#include <grpc/support/string_util.h>
+
+#include "src/core/ext/filters/client_channel/parse_address.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h"
+#include "src/core/ext/filters/client_channel/server_address.h"
+#include "src/core/lib/gpr/host_port.h"
+#include "src/core/lib/gpr/string.h"
+
+bool grpc_ares_query_ipv6() {
+  /* The libuv grpc code currently does not have the code to probe for this,
+   * so we assume for now that IPv6 is always available in contexts where this
+   * code will be used. */
+  return true;
+}
+
+bool grpc_ares_maybe_resolve_localhost_manually_locked(
+    const char* name, const char* default_port,
+    grpc_core::UniquePtr<grpc_core::ServerAddressList>* addrs) {
+  char* host = nullptr;
+  char* port = nullptr;
+  bool out = inner_maybe_resolve_localhost_manually_locked(name, default_port,
+                                                           addrs, &host, &port);
+  gpr_free(host);
+  gpr_free(port);
+  return out;
+}
+
+#endif /* GRPC_ARES == 1 && defined(GRPC_UV) */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc
new file mode 100644
index 0000000..1232fc9
--- /dev/null
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc
@@ -0,0 +1,83 @@
+/*
+ *
+ * Copyright 2019 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 <grpc/support/port_platform.h>
+
+#include "src/core/lib/iomgr/port.h"
+#if GRPC_ARES == 1 && (defined(GRPC_UV) || defined(GPR_WINDOWS))
+
+#include <grpc/support/string_util.h>
+
+#include "src/core/ext/filters/client_channel/parse_address.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h"
+#include "src/core/ext/filters/client_channel/server_address.h"
+#include "src/core/lib/gpr/host_port.h"
+#include "src/core/lib/gpr/string.h"
+
+bool inner_maybe_resolve_localhost_manually_locked(
+    const char* name, const char* default_port,
+    grpc_core::UniquePtr<grpc_core::ServerAddressList>* addrs, char** host,
+    char** port) {
+  gpr_split_host_port(name, host, port);
+  if (*host == nullptr) {
+    gpr_log(GPR_ERROR,
+            "Failed to parse %s into host:port during manual localhost "
+            "resolution check.",
+            name);
+    return false;
+  }
+  if (*port == nullptr) {
+    if (default_port == nullptr) {
+      gpr_log(GPR_ERROR,
+              "No port or default port for %s during manual localhost "
+              "resolution check.",
+              name);
+      return false;
+    }
+    *port = gpr_strdup(default_port);
+  }
+  if (gpr_stricmp(*host, "localhost") == 0) {
+    GPR_ASSERT(*addrs == nullptr);
+    *addrs = grpc_core::MakeUnique<grpc_core::ServerAddressList>();
+    uint16_t numeric_port = grpc_strhtons(*port);
+    // Append the ipv6 loopback address.
+    struct sockaddr_in6 ipv6_loopback_addr;
+    memset(&ipv6_loopback_addr, 0, sizeof(ipv6_loopback_addr));
+    ((char*)&ipv6_loopback_addr.sin6_addr)[15] = 1;
+    ipv6_loopback_addr.sin6_family = AF_INET6;
+    ipv6_loopback_addr.sin6_port = numeric_port;
+    (*addrs)->emplace_back(&ipv6_loopback_addr, sizeof(ipv6_loopback_addr),
+                           nullptr /* args */);
+    // Append the ipv4 loopback address.
+    struct sockaddr_in ipv4_loopback_addr;
+    memset(&ipv4_loopback_addr, 0, sizeof(ipv4_loopback_addr));
+    ((char*)&ipv4_loopback_addr.sin_addr)[0] = 0x7f;
+    ((char*)&ipv4_loopback_addr.sin_addr)[3] = 0x01;
+    ipv4_loopback_addr.sin_family = AF_INET;
+    ipv4_loopback_addr.sin_port = numeric_port;
+    (*addrs)->emplace_back(&ipv4_loopback_addr, sizeof(ipv4_loopback_addr),
+                           nullptr /* args */);
+    // Let the address sorter figure out which one should be tried first.
+    grpc_cares_wrapper_address_sorting_sort(addrs->get());
+    return true;
+  }
+  return false;
+}
+
+#endif /* GRPC_ARES == 1 && (defined(GRPC_UV) || defined(GPR_WINDOWS)) */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h
new file mode 100644
index 0000000..ed40e58
--- /dev/null
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h
@@ -0,0 +1,34 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_C_ARES_GRPC_ARES_WRAPPER_LIBUV_WINDOWS_H
+#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_C_ARES_GRPC_ARES_WRAPPER_LIBUV_WINDOWS_H
+
+#include <grpc/support/port_platform.h>
+
+#include <grpc/support/string_util.h>
+
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+
+bool inner_maybe_resolve_localhost_manually_locked(
+    const char* name, const char* default_port,
+    grpc_core::UniquePtr<grpc_core::ServerAddressList>* addrs, char** host,
+    char** port);
+
+#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_C_ARES_GRPC_ARES_WRAPPER_LIBUV_WINDOWS_H \
+        */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc
index 202452f..60398c6 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc
@@ -25,6 +25,7 @@
 
 #include "src/core/ext/filters/client_channel/parse_address.h"
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
@@ -32,56 +33,6 @@
 
 bool grpc_ares_query_ipv6() { return grpc_ipv6_loopback_available(); }
 
-static bool inner_maybe_resolve_localhost_manually_locked(
-    const char* name, const char* default_port,
-    grpc_core::UniquePtr<grpc_core::ServerAddressList>* addrs, char** host,
-    char** port) {
-  gpr_split_host_port(name, host, port);
-  if (*host == nullptr) {
-    gpr_log(GPR_ERROR,
-            "Failed to parse %s into host:port during Windows localhost "
-            "resolution check.",
-            name);
-    return false;
-  }
-  if (*port == nullptr) {
-    if (default_port == nullptr) {
-      gpr_log(GPR_ERROR,
-              "No port or default port for %s during Windows localhost "
-              "resolution check.",
-              name);
-      return false;
-    }
-    *port = gpr_strdup(default_port);
-  }
-  if (gpr_stricmp(*host, "localhost") == 0) {
-    GPR_ASSERT(*addrs == nullptr);
-    *addrs = grpc_core::MakeUnique<grpc_core::ServerAddressList>();
-    uint16_t numeric_port = grpc_strhtons(*port);
-    // Append the ipv6 loopback address.
-    struct sockaddr_in6 ipv6_loopback_addr;
-    memset(&ipv6_loopback_addr, 0, sizeof(ipv6_loopback_addr));
-    ((char*)&ipv6_loopback_addr.sin6_addr)[15] = 1;
-    ipv6_loopback_addr.sin6_family = AF_INET6;
-    ipv6_loopback_addr.sin6_port = numeric_port;
-    (*addrs)->emplace_back(&ipv6_loopback_addr, sizeof(ipv6_loopback_addr),
-                           nullptr /* args */);
-    // Append the ipv4 loopback address.
-    struct sockaddr_in ipv4_loopback_addr;
-    memset(&ipv4_loopback_addr, 0, sizeof(ipv4_loopback_addr));
-    ((char*)&ipv4_loopback_addr.sin_addr)[0] = 0x7f;
-    ((char*)&ipv4_loopback_addr.sin_addr)[3] = 0x01;
-    ipv4_loopback_addr.sin_family = AF_INET;
-    ipv4_loopback_addr.sin_port = numeric_port;
-    (*addrs)->emplace_back(&ipv4_loopback_addr, sizeof(ipv4_loopback_addr),
-                           nullptr /* args */);
-    // Let the address sorter figure out which one should be tried first.
-    grpc_cares_wrapper_address_sorting_sort(addrs->get());
-    return true;
-  }
-  return false;
-}
-
 bool grpc_ares_maybe_resolve_localhost_manually_locked(
     const char* name, const char* default_port,
     grpc_core::UniquePtr<grpc_core::ServerAddressList>* addrs) {
diff --git a/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc b/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc
new file mode 100644
index 0000000..07a617c
--- /dev/null
+++ b/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc
@@ -0,0 +1,28 @@
+//
+// Copyright 2019 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.
+//
+
+// This is similar to the sockaddr resolver, except that it supports a
+// bunch of query args that are useful for dependency injection in tests.
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
+
+GPR_GLOBAL_CONFIG_DEFINE_STRING(
+    grpc_dns_resolver, "",
+    "Declares which DNS resolver to use. The default is ares if gRPC is built "
+    "with c-ares support. Otherwise, the value of this environment variable is "
+    "ignored.")
diff --git a/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h b/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h
new file mode 100644
index 0000000..d0a3486
--- /dev/null
+++ b/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_DNS_RESOLVER_SELECTION_H
+#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_DNS_RESOLVER_SELECTION_H
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/lib/gprpp/global_config.h"
+
+GPR_GLOBAL_CONFIG_DECLARE_STRING(grpc_dns_resolver);
+
+#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_DNS_RESOLVER_SELECTION_H \
+        */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc b/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
index 164d308..5ab75d0 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
@@ -26,11 +26,11 @@
 #include <grpc/support/string_util.h>
 #include <grpc/support/time.h>
 
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/lib/backoff/backoff.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
@@ -274,8 +274,9 @@
 }  // namespace grpc_core
 
 void grpc_resolver_dns_native_init() {
-  char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (resolver_env != nullptr && gpr_stricmp(resolver_env, "native") == 0) {
+  grpc_core::UniquePtr<char> resolver =
+      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+  if (gpr_stricmp(resolver.get(), "native") == 0) {
     gpr_log(GPR_DEBUG, "Using native dns resolver");
     grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
         grpc_core::UniquePtr<grpc_core::ResolverFactory>(
@@ -291,7 +292,6 @@
               grpc_core::New<grpc_core::NativeDnsResolverFactory>()));
     }
   }
-  gpr_free(resolver_env);
 }
 
 void grpc_resolver_dns_native_shutdown() {}
diff --git a/src/core/ext/filters/client_channel/resolver_result_parsing.cc b/src/core/ext/filters/client_channel/resolver_result_parsing.cc
index daac4f0..ac6bc7e 100644
--- a/src/core/ext/filters/client_channel/resolver_result_parsing.cc
+++ b/src/core/ext/filters/client_channel/resolver_result_parsing.cc
@@ -35,6 +35,7 @@
 #include "src/core/lib/channel/status_util.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/gprpp/optional.h"
 #include "src/core/lib/uri/uri_parser.h"
 
 // As per the retry design, we do not allow more than 5 retry attempts.
@@ -43,203 +44,22 @@
 namespace grpc_core {
 namespace internal {
 
-ProcessedResolverResult::ProcessedResolverResult(
-    Resolver::Result* resolver_result, bool parse_retry)
-    : service_config_(resolver_result->service_config) {
-  // If resolver did not return a service config, use the default
-  // specified via the client API.
-  if (service_config_ == nullptr) {
-    const char* service_config_json = grpc_channel_arg_get_string(
-        grpc_channel_args_find(resolver_result->args, GRPC_ARG_SERVICE_CONFIG));
-    if (service_config_json != nullptr) {
-      service_config_ = ServiceConfig::Create(service_config_json);
-    }
-  } else {
-    // Add the service config JSON to channel args so that it's
-    // accessible in the subchannel.
-    // TODO(roth): Consider whether there's a better way to pass the
-    // service config down into the subchannel stack, such as maybe via
-    // call context or metadata.  This would avoid the problem of having
-    // to recreate all subchannels whenever the service config changes.
-    // It would also avoid the need to pass in the resolver result in
-    // mutable form, both here and in
-    // ResolvingLoadBalancingPolicy::ProcessResolverResultCallback().
-    grpc_arg arg = grpc_channel_arg_string_create(
-        const_cast<char*>(GRPC_ARG_SERVICE_CONFIG),
-        const_cast<char*>(service_config_->service_config_json()));
-    grpc_channel_args* new_args =
-        grpc_channel_args_copy_and_add(resolver_result->args, &arg, 1);
-    grpc_channel_args_destroy(resolver_result->args);
-    resolver_result->args = new_args;
-  }
-  // Process service config.
-  ProcessServiceConfig(*resolver_result, parse_retry);
-  // If no LB config was found above, just find the LB policy name then.
-  if (lb_policy_name_ == nullptr) ProcessLbPolicyName(*resolver_result);
+namespace {
+size_t g_client_channel_service_config_parser_index;
 }
 
-void ProcessedResolverResult::ProcessServiceConfig(
-    const Resolver::Result& resolver_result, bool parse_retry) {
-  if (service_config_ == nullptr) return;
-  service_config_json_ =
-      UniquePtr<char>(gpr_strdup(service_config_->service_config_json()));
-  if (parse_retry) {
-    const grpc_arg* channel_arg =
-        grpc_channel_args_find(resolver_result.args, GRPC_ARG_SERVER_URI);
-    const char* server_uri = grpc_channel_arg_get_string(channel_arg);
-    GPR_ASSERT(server_uri != nullptr);
-    grpc_uri* uri = grpc_uri_parse(server_uri, true);
-    GPR_ASSERT(uri->path[0] != '\0');
-    server_name_ = uri->path[0] == '/' ? uri->path + 1 : uri->path;
-    service_config_->ParseGlobalParams(ParseServiceConfig, this);
-    grpc_uri_destroy(uri);
-  } else {
-    service_config_->ParseGlobalParams(ParseServiceConfig, this);
-  }
-  method_params_table_ = service_config_->CreateMethodConfigTable(
-      ClientChannelMethodParams::CreateFromJson);
+size_t ClientChannelServiceConfigParser::ParserIndex() {
+  return g_client_channel_service_config_parser_index;
 }
 
-void ProcessedResolverResult::ProcessLbPolicyName(
-    const Resolver::Result& resolver_result) {
-  // Prefer the LB policy name found in the service config. Note that this is
-  // checking the deprecated loadBalancingPolicy field, rather than the new
-  // loadBalancingConfig field.
-  if (service_config_ != nullptr) {
-    lb_policy_name_.reset(
-        gpr_strdup(service_config_->GetLoadBalancingPolicyName()));
-    // Convert to lower-case.
-    if (lb_policy_name_ != nullptr) {
-      char* lb_policy_name = lb_policy_name_.get();
-      for (size_t i = 0; i < strlen(lb_policy_name); ++i) {
-        lb_policy_name[i] = tolower(lb_policy_name[i]);
-      }
-    }
-  }
-  // Otherwise, find the LB policy name set by the client API.
-  if (lb_policy_name_ == nullptr) {
-    const grpc_arg* channel_arg =
-        grpc_channel_args_find(resolver_result.args, GRPC_ARG_LB_POLICY_NAME);
-    lb_policy_name_.reset(gpr_strdup(grpc_channel_arg_get_string(channel_arg)));
-  }
-  // Special case: If at least one balancer address is present, we use
-  // the grpclb policy, regardless of what the resolver has returned.
-  bool found_balancer_address = false;
-  for (size_t i = 0; i < resolver_result.addresses.size(); ++i) {
-    const ServerAddress& address = resolver_result.addresses[i];
-    if (address.IsBalancer()) {
-      found_balancer_address = true;
-      break;
-    }
-  }
-  if (found_balancer_address) {
-    if (lb_policy_name_ != nullptr &&
-        strcmp(lb_policy_name_.get(), "grpclb") != 0) {
-      gpr_log(GPR_INFO,
-              "resolver requested LB policy %s but provided at least one "
-              "balancer address -- forcing use of grpclb LB policy",
-              lb_policy_name_.get());
-    }
-    lb_policy_name_.reset(gpr_strdup("grpclb"));
-  }
-  // Use pick_first if nothing was specified and we didn't select grpclb
-  // above.
-  if (lb_policy_name_ == nullptr) {
-    lb_policy_name_.reset(gpr_strdup("pick_first"));
-  }
-}
-
-void ProcessedResolverResult::ParseServiceConfig(
-    const grpc_json* field, ProcessedResolverResult* parsing_state) {
-  parsing_state->ParseLbConfigFromServiceConfig(field);
-  if (parsing_state->server_name_ != nullptr) {
-    parsing_state->ParseRetryThrottleParamsFromServiceConfig(field);
-  }
-}
-
-void ProcessedResolverResult::ParseLbConfigFromServiceConfig(
-    const grpc_json* field) {
-  if (lb_policy_config_ != nullptr) return;  // Already found.
-  if (field->key == nullptr || strcmp(field->key, "loadBalancingConfig") != 0) {
-    return;  // Not the LB config global parameter.
-  }
-  const grpc_json* policy =
-      LoadBalancingPolicy::ParseLoadBalancingConfig(field);
-  if (policy != nullptr) {
-    lb_policy_name_.reset(gpr_strdup(policy->key));
-    lb_policy_config_ =
-        MakeRefCounted<LoadBalancingPolicy::Config>(policy, service_config_);
-  }
-}
-
-void ProcessedResolverResult::ParseRetryThrottleParamsFromServiceConfig(
-    const grpc_json* field) {
-  if (strcmp(field->key, "retryThrottling") == 0) {
-    if (retry_throttle_data_ != nullptr) return;  // Duplicate.
-    if (field->type != GRPC_JSON_OBJECT) return;
-    int max_milli_tokens = 0;
-    int milli_token_ratio = 0;
-    for (grpc_json* sub_field = field->child; sub_field != nullptr;
-         sub_field = sub_field->next) {
-      if (sub_field->key == nullptr) return;
-      if (strcmp(sub_field->key, "maxTokens") == 0) {
-        if (max_milli_tokens != 0) return;  // Duplicate.
-        if (sub_field->type != GRPC_JSON_NUMBER) return;
-        max_milli_tokens = gpr_parse_nonnegative_int(sub_field->value);
-        if (max_milli_tokens == -1) return;
-        max_milli_tokens *= 1000;
-      } else if (strcmp(sub_field->key, "tokenRatio") == 0) {
-        if (milli_token_ratio != 0) return;  // Duplicate.
-        if (sub_field->type != GRPC_JSON_NUMBER) return;
-        // We support up to 3 decimal digits.
-        size_t whole_len = strlen(sub_field->value);
-        uint32_t multiplier = 1;
-        uint32_t decimal_value = 0;
-        const char* decimal_point = strchr(sub_field->value, '.');
-        if (decimal_point != nullptr) {
-          whole_len = static_cast<size_t>(decimal_point - sub_field->value);
-          multiplier = 1000;
-          size_t decimal_len = strlen(decimal_point + 1);
-          if (decimal_len > 3) decimal_len = 3;
-          if (!gpr_parse_bytes_to_uint32(decimal_point + 1, decimal_len,
-                                         &decimal_value)) {
-            return;
-          }
-          uint32_t decimal_multiplier = 1;
-          for (size_t i = 0; i < (3 - decimal_len); ++i) {
-            decimal_multiplier *= 10;
-          }
-          decimal_value *= decimal_multiplier;
-        }
-        uint32_t whole_value;
-        if (!gpr_parse_bytes_to_uint32(sub_field->value, whole_len,
-                                       &whole_value)) {
-          return;
-        }
-        milli_token_ratio =
-            static_cast<int>((whole_value * multiplier) + decimal_value);
-        if (milli_token_ratio <= 0) return;
-      }
-    }
-    retry_throttle_data_ =
-        grpc_core::internal::ServerRetryThrottleMap::GetDataForServer(
-            server_name_, max_milli_tokens, milli_token_ratio);
-  }
+void ClientChannelServiceConfigParser::Register() {
+  g_client_channel_service_config_parser_index =
+      ServiceConfig::RegisterParser(UniquePtr<ServiceConfig::Parser>(
+          New<ClientChannelServiceConfigParser>()));
 }
 
 namespace {
 
-bool ParseWaitForReady(
-    grpc_json* field, ClientChannelMethodParams::WaitForReady* wait_for_ready) {
-  if (field->type != GRPC_JSON_TRUE && field->type != GRPC_JSON_FALSE) {
-    return false;
-  }
-  *wait_for_ready = field->type == GRPC_JSON_TRUE
-                        ? ClientChannelMethodParams::WAIT_FOR_READY_TRUE
-                        : ClientChannelMethodParams::WAIT_FOR_READY_FALSE;
-  return true;
-}
-
 // Parses a JSON field of the form generated for a google.proto.Duration
 // proto message, as per:
 //   https://developers.google.com/protocol-buffers/docs/proto3#json
@@ -272,18 +92,36 @@
   return true;
 }
 
-UniquePtr<ClientChannelMethodParams::RetryPolicy> ParseRetryPolicy(
-    grpc_json* field) {
-  auto retry_policy = MakeUnique<ClientChannelMethodParams::RetryPolicy>();
-  if (field->type != GRPC_JSON_OBJECT) return nullptr;
+UniquePtr<ClientChannelMethodParsedObject::RetryPolicy> ParseRetryPolicy(
+    grpc_json* field, grpc_error** error) {
+  GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+  auto retry_policy =
+      MakeUnique<ClientChannelMethodParsedObject::RetryPolicy>();
+  if (field->type != GRPC_JSON_OBJECT) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "field:retryPolicy error:should be of type object");
+    return nullptr;
+  }
+  InlinedVector<grpc_error*, 4> error_list;
   for (grpc_json* sub_field = field->child; sub_field != nullptr;
        sub_field = sub_field->next) {
-    if (sub_field->key == nullptr) return nullptr;
+    if (sub_field->key == nullptr) continue;
     if (strcmp(sub_field->key, "maxAttempts") == 0) {
-      if (retry_policy->max_attempts != 0) return nullptr;  // Duplicate.
-      if (sub_field->type != GRPC_JSON_NUMBER) return nullptr;
+      if (retry_policy->max_attempts != 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxAttempts error:Duplicate entry"));
+      }  // Duplicate. Continue Parsing
+      if (sub_field->type != GRPC_JSON_NUMBER) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxAttempts error:should be of type number"));
+        continue;
+      }
       retry_policy->max_attempts = gpr_parse_nonnegative_int(sub_field->value);
-      if (retry_policy->max_attempts <= 1) return nullptr;
+      if (retry_policy->max_attempts <= 1) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxAttempts error:should be at least 2"));
+        continue;
+      }
       if (retry_policy->max_attempts > MAX_MAX_RETRY_ATTEMPTS) {
         gpr_log(GPR_ERROR,
                 "service config: clamped retryPolicy.maxAttempts at %d",
@@ -291,78 +129,375 @@
         retry_policy->max_attempts = MAX_MAX_RETRY_ATTEMPTS;
       }
     } else if (strcmp(sub_field->key, "initialBackoff") == 0) {
-      if (retry_policy->initial_backoff > 0) return nullptr;  // Duplicate.
+      if (retry_policy->initial_backoff > 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:initialBackoff error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
       if (!ParseDuration(sub_field, &retry_policy->initial_backoff)) {
-        return nullptr;
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:initialBackoff error:Failed to parse"));
+        continue;
       }
-      if (retry_policy->initial_backoff == 0) return nullptr;
+      if (retry_policy->initial_backoff == 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:initialBackoff error:must be greater than 0"));
+      }
     } else if (strcmp(sub_field->key, "maxBackoff") == 0) {
-      if (retry_policy->max_backoff > 0) return nullptr;  // Duplicate.
+      if (retry_policy->max_backoff > 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxBackoff error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
       if (!ParseDuration(sub_field, &retry_policy->max_backoff)) {
-        return nullptr;
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxBackoff error:failed to parse"));
+        continue;
       }
-      if (retry_policy->max_backoff == 0) return nullptr;
+      if (retry_policy->max_backoff == 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxBackoff error:should be greater than 0"));
+      }
     } else if (strcmp(sub_field->key, "backoffMultiplier") == 0) {
-      if (retry_policy->backoff_multiplier != 0) return nullptr;  // Duplicate.
-      if (sub_field->type != GRPC_JSON_NUMBER) return nullptr;
+      if (retry_policy->backoff_multiplier != 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:backoffMultiplier error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      if (sub_field->type != GRPC_JSON_NUMBER) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:backoffMultiplier error:should be of type number"));
+        continue;
+      }
       if (sscanf(sub_field->value, "%f", &retry_policy->backoff_multiplier) !=
           1) {
-        return nullptr;
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:backoffMultiplier error:failed to parse"));
+        continue;
       }
-      if (retry_policy->backoff_multiplier <= 0) return nullptr;
+      if (retry_policy->backoff_multiplier <= 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:backoffMultiplier error:should be greater than 0"));
+      }
     } else if (strcmp(sub_field->key, "retryableStatusCodes") == 0) {
       if (!retry_policy->retryable_status_codes.Empty()) {
-        return nullptr;  // Duplicate.
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryableStatusCodes error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      if (sub_field->type != GRPC_JSON_ARRAY) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryableStatusCodes error:should be of type array"));
+        continue;
       }
-      if (sub_field->type != GRPC_JSON_ARRAY) return nullptr;
       for (grpc_json* element = sub_field->child; element != nullptr;
            element = element->next) {
-        if (element->type != GRPC_JSON_STRING) return nullptr;
+        if (element->type != GRPC_JSON_STRING) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:retryableStatusCodes error:status codes should be of type "
+              "string"));
+          continue;
+        }
         grpc_status_code status;
         if (!grpc_status_code_from_string(element->value, &status)) {
-          return nullptr;
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:retryableStatusCodes error:failed to parse status code"));
+          continue;
         }
         retry_policy->retryable_status_codes.Add(status);
       }
-      if (retry_policy->retryable_status_codes.Empty()) return nullptr;
+      if (retry_policy->retryable_status_codes.Empty()) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryableStatusCodes error:should be non-empty"));
+      };
     }
   }
   // Make sure required fields are set.
-  if (retry_policy->max_attempts == 0 || retry_policy->initial_backoff == 0 ||
-      retry_policy->max_backoff == 0 || retry_policy->backoff_multiplier == 0 ||
-      retry_policy->retryable_status_codes.Empty()) {
+  if (error_list.empty()) {
+    if (retry_policy->max_attempts == 0 || retry_policy->initial_backoff == 0 ||
+        retry_policy->max_backoff == 0 ||
+        retry_policy->backoff_multiplier == 0 ||
+        retry_policy->retryable_status_codes.Empty()) {
+      *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "field:retryPolicy error:Missing required field(s)");
+      return nullptr;
+    }
+  }
+  *error = GRPC_ERROR_CREATE_FROM_VECTOR("retryPolicy", &error_list);
+  return *error == GRPC_ERROR_NONE ? std::move(retry_policy) : nullptr;
+}
+
+const char* ParseHealthCheckConfig(const grpc_json* field, grpc_error** error) {
+  GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+  const char* service_name = nullptr;
+  GPR_DEBUG_ASSERT(strcmp(field->key, "healthCheckConfig") == 0);
+  if (field->type != GRPC_JSON_OBJECT) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "field:healthCheckConfig error:should be of type object");
     return nullptr;
   }
-  return retry_policy;
+  InlinedVector<grpc_error*, 2> error_list;
+  for (grpc_json* sub_field = field->child; sub_field != nullptr;
+       sub_field = sub_field->next) {
+    if (sub_field->key == nullptr) {
+      GPR_DEBUG_ASSERT(false);
+      continue;
+    }
+    if (strcmp(sub_field->key, "serviceName") == 0) {
+      if (service_name != nullptr) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:serviceName error:Duplicate "
+            "entry"));
+      }  // Duplicate. Continue parsing
+      if (sub_field->type != GRPC_JSON_STRING) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:serviceName error:should be of type string"));
+        continue;
+      }
+      service_name = sub_field->value;
+    }
+  }
+  if (!error_list.empty()) {
+    return nullptr;
+  }
+  *error =
+      GRPC_ERROR_CREATE_FROM_VECTOR("field:healthCheckConfig", &error_list);
+  return service_name;
 }
 
 }  // namespace
 
-RefCountedPtr<ClientChannelMethodParams>
-ClientChannelMethodParams::CreateFromJson(const grpc_json* json) {
-  RefCountedPtr<ClientChannelMethodParams> method_params =
-      MakeRefCounted<ClientChannelMethodParams>();
+UniquePtr<ServiceConfig::ParsedConfig>
+ClientChannelServiceConfigParser::ParseGlobalParams(const grpc_json* json,
+                                                    grpc_error** error) {
+  GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+  InlinedVector<grpc_error*, 4> error_list;
+  RefCountedPtr<ParsedLoadBalancingConfig> parsed_lb_config;
+  UniquePtr<char> lb_policy_name;
+  Optional<ClientChannelGlobalParsedObject::RetryThrottling> retry_throttling;
+  const char* health_check_service_name = nullptr;
+  for (grpc_json* field = json->child; field != nullptr; field = field->next) {
+    if (field->key == nullptr) {
+      continue;  // Not the LB config global parameter
+    }
+    // Parsed Load balancing config
+    if (strcmp(field->key, "loadBalancingConfig") == 0) {
+      if (parsed_lb_config != nullptr) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:loadBalancingConfig error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      grpc_error* parse_error = GRPC_ERROR_NONE;
+      parsed_lb_config = LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(
+          field, &parse_error);
+      if (parsed_lb_config == nullptr) {
+        error_list.push_back(parse_error);
+      }
+    }
+    // Parse deprecated loadBalancingPolicy
+    if (strcmp(field->key, "loadBalancingPolicy") == 0) {
+      if (lb_policy_name != nullptr) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:loadBalancingPolicy error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      if (field->type != GRPC_JSON_STRING) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:loadBalancingPolicy error:type should be string"));
+        continue;
+      }
+      lb_policy_name.reset(gpr_strdup(field->value));
+      char* lb_policy = lb_policy_name.get();
+      if (lb_policy != nullptr) {
+        for (size_t i = 0; i < strlen(lb_policy); ++i) {
+          lb_policy[i] = tolower(lb_policy[i]);
+        }
+      }
+      bool requires_config = false;
+      if (!LoadBalancingPolicyRegistry::LoadBalancingPolicyExists(
+              lb_policy, &requires_config)) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:loadBalancingPolicy error:Unknown lb policy"));
+      } else if (requires_config) {
+        char* error_msg;
+        gpr_asprintf(&error_msg,
+                     "field:loadBalancingPolicy error:%s requires a config. "
+                     "Please use loadBalancingConfig instead.",
+                     lb_policy);
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg));
+        gpr_free(error_msg);
+      }
+    }
+    // Parse retry throttling
+    if (strcmp(field->key, "retryThrottling") == 0) {
+      if (retry_throttling.has_value()) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryThrottling error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      if (field->type != GRPC_JSON_OBJECT) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryThrottling error:Type should be object"));
+        continue;
+      }
+      Optional<int> max_milli_tokens;
+      Optional<int> milli_token_ratio;
+      for (grpc_json* sub_field = field->child; sub_field != nullptr;
+           sub_field = sub_field->next) {
+        if (sub_field->key == nullptr) continue;
+        if (strcmp(sub_field->key, "maxTokens") == 0) {
+          if (max_milli_tokens.has_value()) {
+            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                "field:retryThrottling field:maxTokens error:Duplicate "
+                "entry"));
+          }  // Duplicate, continue parsing.
+          if (sub_field->type != GRPC_JSON_NUMBER) {
+            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                "field:retryThrottling field:maxTokens error:Type should be "
+                "number"));
+          } else {
+            max_milli_tokens.set(gpr_parse_nonnegative_int(sub_field->value) *
+                                 1000);
+            if (max_milli_tokens.value() <= 0) {
+              error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                  "field:retryThrottling field:maxTokens error:should be "
+                  "greater than zero"));
+            }
+          }
+        } else if (strcmp(sub_field->key, "tokenRatio") == 0) {
+          if (milli_token_ratio.has_value()) {
+            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                "field:retryThrottling field:tokenRatio error:Duplicate "
+                "entry"));
+          }  // Duplicate, continue parsing.
+          if (sub_field->type != GRPC_JSON_NUMBER) {
+            error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                "field:retryThrottling field:tokenRatio error:type should be "
+                "number"));
+          } else {
+            // We support up to 3 decimal digits.
+            size_t whole_len = strlen(sub_field->value);
+            uint32_t multiplier = 1;
+            uint32_t decimal_value = 0;
+            const char* decimal_point = strchr(sub_field->value, '.');
+            if (decimal_point != nullptr) {
+              whole_len = static_cast<size_t>(decimal_point - sub_field->value);
+              multiplier = 1000;
+              size_t decimal_len = strlen(decimal_point + 1);
+              if (decimal_len > 3) decimal_len = 3;
+              if (!gpr_parse_bytes_to_uint32(decimal_point + 1, decimal_len,
+                                             &decimal_value)) {
+                error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                    "field:retryThrottling field:tokenRatio error:Failed "
+                    "parsing"));
+                continue;
+              }
+              uint32_t decimal_multiplier = 1;
+              for (size_t i = 0; i < (3 - decimal_len); ++i) {
+                decimal_multiplier *= 10;
+              }
+              decimal_value *= decimal_multiplier;
+            }
+            uint32_t whole_value;
+            if (!gpr_parse_bytes_to_uint32(sub_field->value, whole_len,
+                                           &whole_value)) {
+              error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                  "field:retryThrottling field:tokenRatio error:Failed "
+                  "parsing"));
+              continue;
+            }
+            milli_token_ratio.set(
+                static_cast<int>((whole_value * multiplier) + decimal_value));
+            if (milli_token_ratio.value() <= 0) {
+              error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                  "field:retryThrottling field:tokenRatio error:value should "
+                  "be greater than 0"));
+            }
+          }
+        }
+      }
+      ClientChannelGlobalParsedObject::RetryThrottling data;
+      if (!max_milli_tokens.has_value()) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryThrottling field:maxTokens error:Not found"));
+      } else {
+        data.max_milli_tokens = max_milli_tokens.value();
+      }
+      if (!milli_token_ratio.has_value()) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryThrottling field:tokenRatio error:Not found"));
+      } else {
+        data.milli_token_ratio = milli_token_ratio.value();
+      }
+      retry_throttling.set(data);
+    }
+    if (strcmp(field->key, "healthCheckConfig") == 0) {
+      if (health_check_service_name != nullptr) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:healthCheckConfig error:Duplicate entry"));
+      }  // Duplicate continue parsing
+      grpc_error* parsing_error = GRPC_ERROR_NONE;
+      health_check_service_name = ParseHealthCheckConfig(field, &parsing_error);
+      if (parsing_error != GRPC_ERROR_NONE) {
+        error_list.push_back(parsing_error);
+      }
+    }
+  }
+  *error = GRPC_ERROR_CREATE_FROM_VECTOR("Client channel global parser",
+                                         &error_list);
+  if (*error == GRPC_ERROR_NONE) {
+    return UniquePtr<ServiceConfig::ParsedConfig>(
+        New<ClientChannelGlobalParsedObject>(
+            std::move(parsed_lb_config), std::move(lb_policy_name),
+            retry_throttling, health_check_service_name));
+  }
+  return nullptr;
+}
+
+UniquePtr<ServiceConfig::ParsedConfig>
+ClientChannelServiceConfigParser::ParsePerMethodParams(const grpc_json* json,
+                                                       grpc_error** error) {
+  GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+  InlinedVector<grpc_error*, 4> error_list;
+  Optional<bool> wait_for_ready;
+  grpc_millis timeout = 0;
+  UniquePtr<ClientChannelMethodParsedObject::RetryPolicy> retry_policy;
   for (grpc_json* field = json->child; field != nullptr; field = field->next) {
     if (field->key == nullptr) continue;
     if (strcmp(field->key, "waitForReady") == 0) {
-      if (method_params->wait_for_ready_ != WAIT_FOR_READY_UNSET) {
-        return nullptr;  // Duplicate.
-      }
-      if (!ParseWaitForReady(field, &method_params->wait_for_ready_)) {
-        return nullptr;
+      if (wait_for_ready.has_value()) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:waitForReady error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      if (field->type == GRPC_JSON_TRUE) {
+        wait_for_ready.set(true);
+      } else if (field->type == GRPC_JSON_FALSE) {
+        wait_for_ready.set(false);
+      } else {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:waitForReady error:Type should be true/false"));
       }
     } else if (strcmp(field->key, "timeout") == 0) {
-      if (method_params->timeout_ > 0) return nullptr;  // Duplicate.
-      if (!ParseDuration(field, &method_params->timeout_)) return nullptr;
+      if (timeout > 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:timeout error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      if (!ParseDuration(field, &timeout)) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:timeout error:Failed parsing"));
+      };
     } else if (strcmp(field->key, "retryPolicy") == 0) {
-      if (method_params->retry_policy_ != nullptr) {
-        return nullptr;  // Duplicate.
+      if (retry_policy != nullptr) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:retryPolicy error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      grpc_error* error = GRPC_ERROR_NONE;
+      retry_policy = ParseRetryPolicy(field, &error);
+      if (retry_policy == nullptr) {
+        error_list.push_back(error);
       }
-      method_params->retry_policy_ = ParseRetryPolicy(field);
-      if (method_params->retry_policy_ == nullptr) return nullptr;
     }
   }
-  return method_params;
+  *error = GRPC_ERROR_CREATE_FROM_VECTOR("Client channel parser", &error_list);
+  if (*error == GRPC_ERROR_NONE) {
+    return UniquePtr<ServiceConfig::ParsedConfig>(
+        New<ClientChannelMethodParsedObject>(timeout, wait_for_ready,
+                                             std::move(retry_policy)));
+  }
+  return nullptr;
 }
 
 }  // namespace internal
diff --git a/src/core/ext/filters/client_channel/resolver_result_parsing.h b/src/core/ext/filters/client_channel/resolver_result_parsing.h
index 1a46278..9af8b16 100644
--- a/src/core/ext/filters/client_channel/resolver_result_parsing.h
+++ b/src/core/ext/filters/client_channel/resolver_result_parsing.h
@@ -22,10 +22,12 @@
 #include <grpc/support/port_platform.h>
 
 #include "src/core/ext/filters/client_channel/lb_policy.h"
+#include "src/core/ext/filters/client_channel/lb_policy_factory.h"
 #include "src/core/ext/filters/client_channel/resolver.h"
 #include "src/core/ext/filters/client_channel/retry_throttle.h"
 #include "src/core/ext/filters/client_channel/service_config.h"
 #include "src/core/lib/channel/status_util.h"
+#include "src/core/lib/gprpp/optional.h"
 #include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/exec_ctx.h"  // for grpc_millis
@@ -35,76 +37,48 @@
 namespace grpc_core {
 namespace internal {
 
-class ClientChannelMethodParams;
-
-// A table mapping from a method name to its method parameters.
-typedef SliceHashTable<RefCountedPtr<ClientChannelMethodParams>>
-    ClientChannelMethodParamsTable;
-
-// A container of processed fields from the resolver result. Simplifies the
-// usage of resolver result.
-class ProcessedResolverResult {
+class ClientChannelGlobalParsedObject : public ServiceConfig::ParsedConfig {
  public:
-  // Processes the resolver result and populates the relative members
-  // for later consumption. Tries to parse retry parameters only if parse_retry
-  // is true.
-  ProcessedResolverResult(Resolver::Result* resolver_result, bool parse_retry);
+  struct RetryThrottling {
+    intptr_t max_milli_tokens = 0;
+    intptr_t milli_token_ratio = 0;
+  };
 
-  // Getters. Any managed object's ownership is transferred.
-  UniquePtr<char> service_config_json() {
-    return std::move(service_config_json_);
+  ClientChannelGlobalParsedObject(
+      RefCountedPtr<ParsedLoadBalancingConfig> parsed_lb_config,
+      UniquePtr<char> parsed_deprecated_lb_policy,
+      const Optional<RetryThrottling>& retry_throttling,
+      const char* health_check_service_name)
+      : parsed_lb_config_(std::move(parsed_lb_config)),
+        parsed_deprecated_lb_policy_(std::move(parsed_deprecated_lb_policy)),
+        retry_throttling_(retry_throttling),
+        health_check_service_name_(health_check_service_name) {}
+
+  Optional<RetryThrottling> retry_throttling() const {
+    return retry_throttling_;
   }
-  RefCountedPtr<ServerRetryThrottleData> retry_throttle_data() {
-    return std::move(retry_throttle_data_);
+
+  RefCountedPtr<ParsedLoadBalancingConfig> parsed_lb_config() const {
+    return parsed_lb_config_;
   }
-  RefCountedPtr<ClientChannelMethodParamsTable> method_params_table() {
-    return std::move(method_params_table_);
+
+  const char* parsed_deprecated_lb_policy() const {
+    return parsed_deprecated_lb_policy_.get();
   }
-  UniquePtr<char> lb_policy_name() { return std::move(lb_policy_name_); }
-  RefCountedPtr<LoadBalancingPolicy::Config> lb_policy_config() {
-    return std::move(lb_policy_config_);
+
+  const char* health_check_service_name() const {
+    return health_check_service_name_;
   }
 
  private:
-  // Finds the service config; extracts LB config and (maybe) retry throttle
-  // params from it.
-  void ProcessServiceConfig(const Resolver::Result& resolver_result,
-                            bool parse_retry);
-
-  // Finds the LB policy name (when no LB config was found).
-  void ProcessLbPolicyName(const Resolver::Result& resolver_result);
-
-  // Parses the service config. Intended to be used by
-  // ServiceConfig::ParseGlobalParams.
-  static void ParseServiceConfig(const grpc_json* field,
-                                 ProcessedResolverResult* parsing_state);
-  // Parses the LB config from service config.
-  void ParseLbConfigFromServiceConfig(const grpc_json* field);
-  // Parses the retry throttle parameters from service config.
-  void ParseRetryThrottleParamsFromServiceConfig(const grpc_json* field);
-
-  // Service config.
-  UniquePtr<char> service_config_json_;
-  RefCountedPtr<ServiceConfig> service_config_;
-  // LB policy.
-  UniquePtr<char> lb_policy_name_;
-  RefCountedPtr<LoadBalancingPolicy::Config> lb_policy_config_;
-  // Retry throttle data.
-  char* server_name_ = nullptr;
-  RefCountedPtr<ServerRetryThrottleData> retry_throttle_data_;
-  // Method params table.
-  RefCountedPtr<ClientChannelMethodParamsTable> method_params_table_;
+  RefCountedPtr<ParsedLoadBalancingConfig> parsed_lb_config_;
+  UniquePtr<char> parsed_deprecated_lb_policy_;
+  Optional<RetryThrottling> retry_throttling_;
+  const char* health_check_service_name_;
 };
 
-// The parameters of a method.
-class ClientChannelMethodParams : public RefCounted<ClientChannelMethodParams> {
+class ClientChannelMethodParsedObject : public ServiceConfig::ParsedConfig {
  public:
-  enum WaitForReady {
-    WAIT_FOR_READY_UNSET = 0,
-    WAIT_FOR_READY_FALSE,
-    WAIT_FOR_READY_TRUE
-  };
-
   struct RetryPolicy {
     int max_attempts = 0;
     grpc_millis initial_backoff = 0;
@@ -113,32 +87,37 @@
     StatusCodeSet retryable_status_codes;
   };
 
-  /// Creates a method_parameters object from \a json.
-  /// Intended for use with ServiceConfig::CreateMethodConfigTable().
-  static RefCountedPtr<ClientChannelMethodParams> CreateFromJson(
-      const grpc_json* json);
+  ClientChannelMethodParsedObject(grpc_millis timeout,
+                                  const Optional<bool>& wait_for_ready,
+                                  UniquePtr<RetryPolicy> retry_policy)
+      : timeout_(timeout),
+        wait_for_ready_(wait_for_ready),
+        retry_policy_(std::move(retry_policy)) {}
 
   grpc_millis timeout() const { return timeout_; }
-  WaitForReady wait_for_ready() const { return wait_for_ready_; }
+
+  Optional<bool> wait_for_ready() const { return wait_for_ready_; }
+
   const RetryPolicy* retry_policy() const { return retry_policy_.get(); }
 
  private:
-  // So New() can call our private ctor.
-  template <typename T, typename... Args>
-  friend T* grpc_core::New(Args&&... args);
-
-  // So Delete() can call our private dtor.
-  template <typename T>
-  friend void grpc_core::Delete(T*);
-
-  ClientChannelMethodParams() {}
-  virtual ~ClientChannelMethodParams() {}
-
   grpc_millis timeout_ = 0;
-  WaitForReady wait_for_ready_ = WAIT_FOR_READY_UNSET;
+  Optional<bool> wait_for_ready_;
   UniquePtr<RetryPolicy> retry_policy_;
 };
 
+class ClientChannelServiceConfigParser : public ServiceConfig::Parser {
+ public:
+  UniquePtr<ServiceConfig::ParsedConfig> ParseGlobalParams(
+      const grpc_json* json, grpc_error** error) override;
+
+  UniquePtr<ServiceConfig::ParsedConfig> ParsePerMethodParams(
+      const grpc_json* json, grpc_error** error) override;
+
+  static size_t ParserIndex();
+  static void Register();
+};
+
 }  // namespace internal
 }  // namespace grpc_core
 
diff --git a/src/core/ext/filters/client_channel/resolving_lb_policy.cc b/src/core/ext/filters/client_channel/resolving_lb_policy.cc
index d15af90..b6bc3ea 100644
--- a/src/core/ext/filters/client_channel/resolving_lb_policy.cc
+++ b/src/core/ext/filters/client_channel/resolving_lb_policy.cc
@@ -48,7 +48,7 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/iomgr.h"
 #include "src/core/lib/iomgr/polling_entity.h"
@@ -77,7 +77,7 @@
       : parent_(std::move(parent)) {}
 
   ~ResolverResultHandler() {
-    if (parent_->tracer_->enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(*(parent_->tracer_))) {
       gpr_log(GPR_INFO, "resolving_lb=%p: resolver shutdown complete",
               parent_.get());
     }
@@ -119,27 +119,20 @@
     return parent_->channel_control_helper()->CreateChannel(target, args);
   }
 
-  void UpdateState(grpc_connectivity_state state, grpc_error* state_error,
+  void UpdateState(grpc_connectivity_state state,
                    UniquePtr<SubchannelPicker> picker) override {
-    if (parent_->resolver_ == nullptr) {
-      // shutting down.
-      GRPC_ERROR_UNREF(state_error);
-      return;
-    }
+    if (parent_->resolver_ == nullptr) return;  // Shutting down.
     // If this request is from the pending child policy, ignore it until
     // it reports READY, at which point we swap it into place.
     if (CalledByPendingChild()) {
-      if (parent_->tracer_->enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(*(parent_->tracer_))) {
         gpr_log(GPR_INFO,
                 "resolving_lb=%p helper=%p: pending child policy %p reports "
                 "state=%s",
                 parent_.get(), this, child_,
                 grpc_connectivity_state_name(state));
       }
-      if (state != GRPC_CHANNEL_READY) {
-        GRPC_ERROR_UNREF(state_error);
-        return;
-      }
+      if (state != GRPC_CHANNEL_READY) return;
       grpc_pollset_set_del_pollset_set(
           parent_->lb_policy_->interested_parties(),
           parent_->interested_parties());
@@ -147,11 +140,9 @@
       parent_->lb_policy_ = std::move(parent_->pending_lb_policy_);
     } else if (!CalledByCurrentChild()) {
       // This request is from an outdated child, so ignore it.
-      GRPC_ERROR_UNREF(state_error);
       return;
     }
-    parent_->channel_control_helper()->UpdateState(state, state_error,
-                                                   std::move(picker));
+    parent_->channel_control_helper()->UpdateState(state, std::move(picker));
   }
 
   void RequestReresolution() override {
@@ -160,7 +151,7 @@
     if (parent_->pending_lb_policy_ != nullptr && !CalledByPendingChild()) {
       return;
     }
-    if (parent_->tracer_->enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(*(parent_->tracer_))) {
       gpr_log(GPR_INFO, "resolving_lb=%p: started name re-resolving",
               parent_.get());
     }
@@ -192,7 +183,8 @@
 
 ResolvingLoadBalancingPolicy::ResolvingLoadBalancingPolicy(
     Args args, TraceFlag* tracer, UniquePtr<char> target_uri,
-    UniquePtr<char> child_policy_name, RefCountedPtr<Config> child_lb_config,
+    UniquePtr<char> child_policy_name,
+    RefCountedPtr<ParsedLoadBalancingConfig> child_lb_config,
     grpc_error** error)
     : LoadBalancingPolicy(std::move(args)),
       tracer_(tracer),
@@ -234,8 +226,7 @@
   }
   // Return our picker to the channel.
   channel_control_helper()->UpdateState(
-      GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE,
-      UniquePtr<SubchannelPicker>(New<QueuePicker>(Ref())));
+      GRPC_CHANNEL_IDLE, UniquePtr<SubchannelPicker>(New<QueuePicker>(Ref())));
   return GRPC_ERROR_NONE;
 }
 
@@ -250,7 +241,7 @@
     resolver_.reset();
     MutexLock lock(&lb_policy_mu_);
     if (lb_policy_ != nullptr) {
-      if (tracer_->enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
         gpr_log(GPR_INFO, "resolving_lb=%p: shutting down lb_policy=%p", this,
                 lb_policy_.get());
       }
@@ -259,7 +250,7 @@
       lb_policy_.reset();
     }
     if (pending_lb_policy_ != nullptr) {
-      if (tracer_->enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
         gpr_log(GPR_INFO, "resolving_lb=%p: shutting down pending lb_policy=%p",
                 this, pending_lb_policy_.get());
       }
@@ -307,13 +298,13 @@
 }
 
 void ResolvingLoadBalancingPolicy::StartResolvingLocked() {
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO, "resolving_lb=%p: starting name resolution", this);
   }
   GPR_ASSERT(!started_resolving_);
   started_resolving_ = true;
   channel_control_helper()->UpdateState(
-      GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE,
+      GRPC_CHANNEL_CONNECTING,
       UniquePtr<SubchannelPicker>(New<QueuePicker>(Ref())));
   resolver_->StartLocked();
 }
@@ -323,7 +314,7 @@
     GRPC_ERROR_UNREF(error);
     return;
   }
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO, "resolving_lb=%p: resolver transient failure: %s", this,
             grpc_error_string(error));
   }
@@ -334,14 +325,15 @@
     grpc_error* state_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
         "Resolver transient failure", &error, 1);
     channel_control_helper()->UpdateState(
-        GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(state_error),
+        GRPC_CHANNEL_TRANSIENT_FAILURE,
         UniquePtr<SubchannelPicker>(New<TransientFailurePicker>(state_error)));
   }
   GRPC_ERROR_UNREF(error);
 }
 
 void ResolvingLoadBalancingPolicy::CreateOrUpdateLbPolicyLocked(
-    const char* lb_policy_name, RefCountedPtr<Config> lb_policy_config,
+    const char* lb_policy_name,
+    RefCountedPtr<ParsedLoadBalancingConfig> lb_policy_config,
     Resolver::Result result, TraceStringVector* trace_strings) {
   // If the child policy name changes, we need to create a new child
   // policy.  When this happens, we leave child_policy_ as-is and store
@@ -406,7 +398,7 @@
     // Cases 1, 2b, and 3b: create a new child policy.
     // If lb_policy_ is null, we set it (case 1), else we set
     // pending_lb_policy_ (cases 2b and 3b).
-    if (tracer_->enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
       gpr_log(GPR_INFO, "resolving_lb=%p: Creating new %schild policy %s", this,
               lb_policy_ == nullptr ? "" : "pending ", lb_policy_name);
     }
@@ -427,7 +419,7 @@
   }
   GPR_ASSERT(policy_to_update != nullptr);
   // Update the policy.
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO, "resolving_lb=%p: Updating %schild policy %p", this,
             policy_to_update == pending_lb_policy_.get() ? "pending " : "",
             policy_to_update);
@@ -466,7 +458,7 @@
     return nullptr;
   }
   helper->set_child(lb_policy.get());
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO, "resolving_lb=%p: created new LB policy \"%s\" (%p)",
             this, lb_policy_name, lb_policy.get());
   }
@@ -522,7 +514,7 @@
     Resolver::Result result) {
   // Handle race conditions.
   if (resolver_ == nullptr) return;
-  if (tracer_->enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(*tracer_)) {
     gpr_log(GPR_INFO, "resolving_lb=%p: got resolver result", this);
   }
   // We only want to trace the address resolution in the follow cases:
@@ -538,20 +530,34 @@
   const bool resolution_contains_addresses = result.addresses.size() > 0;
   // Process the resolver result.
   const char* lb_policy_name = nullptr;
-  RefCountedPtr<Config> lb_policy_config;
+  RefCountedPtr<ParsedLoadBalancingConfig> lb_policy_config;
   bool service_config_changed = false;
+  char* service_config_error_string = nullptr;
   if (process_resolver_result_ != nullptr) {
-    service_config_changed =
-        process_resolver_result_(process_resolver_result_user_data_, &result,
-                                 &lb_policy_name, &lb_policy_config);
+    grpc_error* service_config_error = GRPC_ERROR_NONE;
+    service_config_changed = process_resolver_result_(
+        process_resolver_result_user_data_, result, &lb_policy_name,
+        &lb_policy_config, &service_config_error);
+    if (service_config_error != GRPC_ERROR_NONE) {
+      service_config_error_string =
+          gpr_strdup(grpc_error_string(service_config_error));
+      if (lb_policy_name == nullptr) {
+        // Use an empty lb_policy_name as an indicator that we received an
+        // invalid service config and we don't have a fallback service config.
+        OnResolverError(service_config_error);
+      } else {
+        GRPC_ERROR_UNREF(service_config_error);
+      }
+    }
   } else {
     lb_policy_name = child_policy_name_.get();
     lb_policy_config = child_lb_config_;
   }
-  GPR_ASSERT(lb_policy_name != nullptr);
-  // Create or update LB policy, as needed.
-  CreateOrUpdateLbPolicyLocked(lb_policy_name, std::move(lb_policy_config),
-                               std::move(result), &trace_strings);
+  if (lb_policy_name != nullptr) {
+    // Create or update LB policy, as needed.
+    CreateOrUpdateLbPolicyLocked(lb_policy_name, lb_policy_config,
+                                 std::move(result), &trace_strings);
+  }
   // Add channel trace event.
   if (channelz_node() != nullptr) {
     if (service_config_changed) {
@@ -559,10 +565,15 @@
       // config in the trace, at the risk of bloating the trace logs.
       trace_strings.push_back(gpr_strdup("Service config changed"));
     }
+    if (service_config_error_string != nullptr) {
+      trace_strings.push_back(service_config_error_string);
+      service_config_error_string = nullptr;
+    }
     MaybeAddTraceMessagesForAddressChangesLocked(resolution_contains_addresses,
                                                  &trace_strings);
     ConcatenateAndAddChannelTraceLocked(&trace_strings);
   }
+  gpr_free(service_config_error_string);
 }
 
 }  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/resolving_lb_policy.h b/src/core/ext/filters/client_channel/resolving_lb_policy.h
index c934976..b7d99dc 100644
--- a/src/core/ext/filters/client_channel/resolving_lb_policy.h
+++ b/src/core/ext/filters/client_channel/resolving_lb_policy.h
@@ -23,6 +23,7 @@
 
 #include "src/core/ext/filters/client_channel/client_channel_channelz.h"
 #include "src/core/ext/filters/client_channel/lb_policy.h"
+#include "src/core/ext/filters/client_channel/lb_policy_factory.h"
 #include "src/core/ext/filters/client_channel/resolver.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/channel_stack.h"
@@ -53,20 +54,25 @@
  public:
   // If error is set when this returns, then construction failed, and
   // the caller may not use the new object.
-  ResolvingLoadBalancingPolicy(Args args, TraceFlag* tracer,
-                               UniquePtr<char> target_uri,
-                               UniquePtr<char> child_policy_name,
-                               RefCountedPtr<Config> child_lb_config,
-                               grpc_error** error);
+  ResolvingLoadBalancingPolicy(
+      Args args, TraceFlag* tracer, UniquePtr<char> target_uri,
+      UniquePtr<char> child_policy_name,
+      RefCountedPtr<ParsedLoadBalancingConfig> child_lb_config,
+      grpc_error** error);
 
   // Private ctor, to be used by client_channel only!
   //
   // Synchronous callback that takes the resolver result and sets
   // lb_policy_name and lb_policy_config to point to the right data.
   // Returns true if the service config has changed since the last result.
+  // If the returned service_config_error is not none and lb_policy_name is
+  // empty, it means that we don't have a valid service config to use, and we
+  // should set the channel to be in TRANSIENT_FAILURE.
   typedef bool (*ProcessResolverResultCallback)(
-      void* user_data, Resolver::Result* result, const char** lb_policy_name,
-      RefCountedPtr<Config>* lb_policy_config);
+      void* user_data, const Resolver::Result& result,
+      const char** lb_policy_name,
+      RefCountedPtr<ParsedLoadBalancingConfig>* lb_policy_config,
+      grpc_error** service_config_error);
   // If error is set when this returns, then construction failed, and
   // the caller may not use the new object.
   ResolvingLoadBalancingPolicy(
@@ -102,10 +108,10 @@
 
   void StartResolvingLocked();
   void OnResolverError(grpc_error* error);
-  void CreateOrUpdateLbPolicyLocked(const char* lb_policy_name,
-                                    RefCountedPtr<Config> lb_policy_config,
-                                    Resolver::Result result,
-                                    TraceStringVector* trace_strings);
+  void CreateOrUpdateLbPolicyLocked(
+      const char* lb_policy_name,
+      RefCountedPtr<ParsedLoadBalancingConfig> lb_policy_config,
+      Resolver::Result result, TraceStringVector* trace_strings);
   OrphanablePtr<LoadBalancingPolicy> CreateLbPolicyLocked(
       const char* lb_policy_name, const grpc_channel_args& args,
       TraceStringVector* trace_strings);
@@ -121,7 +127,7 @@
   ProcessResolverResultCallback process_resolver_result_ = nullptr;
   void* process_resolver_result_user_data_ = nullptr;
   UniquePtr<char> child_policy_name_;
-  RefCountedPtr<Config> child_lb_config_;
+  RefCountedPtr<ParsedLoadBalancingConfig> child_lb_config_;
 
   // Resolver and associated state.
   OrphanablePtr<Resolver> resolver_;
diff --git a/src/core/ext/filters/client_channel/service_config.cc b/src/core/ext/filters/client_channel/service_config.cc
index bbf671d..86d4f73 100644
--- a/src/core/ext/filters/client_channel/service_config.cc
+++ b/src/core/ext/filters/client_channel/service_config.cc
@@ -33,43 +33,189 @@
 
 namespace grpc_core {
 
-RefCountedPtr<ServiceConfig> ServiceConfig::Create(const char* json) {
+namespace {
+typedef InlinedVector<UniquePtr<ServiceConfig::Parser>,
+                      ServiceConfig::kNumPreallocatedParsers>
+    ServiceConfigParserList;
+ServiceConfigParserList* g_registered_parsers;
+}  // namespace
+
+RefCountedPtr<ServiceConfig> ServiceConfig::Create(const char* json,
+                                                   grpc_error** error) {
   UniquePtr<char> service_config_json(gpr_strdup(json));
   UniquePtr<char> json_string(gpr_strdup(json));
+  GPR_DEBUG_ASSERT(error != nullptr);
   grpc_json* json_tree = grpc_json_parse_string(json_string.get());
   if (json_tree == nullptr) {
-    gpr_log(GPR_INFO, "failed to parse JSON for service config");
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "failed to parse JSON for service config");
     return nullptr;
   }
-  return MakeRefCounted<ServiceConfig>(std::move(service_config_json),
-                                       std::move(json_string), json_tree);
+  return MakeRefCounted<ServiceConfig>(
+      std::move(service_config_json), std::move(json_string), json_tree, error);
 }
 
 ServiceConfig::ServiceConfig(UniquePtr<char> service_config_json,
-                             UniquePtr<char> json_string, grpc_json* json_tree)
+                             UniquePtr<char> json_string, grpc_json* json_tree,
+                             grpc_error** error)
     : service_config_json_(std::move(service_config_json)),
       json_string_(std::move(json_string)),
-      json_tree_(json_tree) {}
-
-ServiceConfig::~ServiceConfig() { grpc_json_destroy(json_tree_); }
-
-const char* ServiceConfig::GetLoadBalancingPolicyName() const {
-  if (json_tree_->type != GRPC_JSON_OBJECT || json_tree_->key != nullptr) {
-    return nullptr;
+      json_tree_(json_tree) {
+  GPR_DEBUG_ASSERT(error != nullptr);
+  if (json_tree->type != GRPC_JSON_OBJECT || json_tree->key != nullptr) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "Malformed service Config JSON object");
+    return;
   }
-  const char* lb_policy_name = nullptr;
-  for (grpc_json* field = json_tree_->child; field != nullptr;
-       field = field->next) {
-    if (field->key == nullptr) return nullptr;
-    if (strcmp(field->key, "loadBalancingPolicy") == 0) {
-      if (lb_policy_name != nullptr) return nullptr;  // Duplicate.
-      if (field->type != GRPC_JSON_STRING) return nullptr;
-      lb_policy_name = field->value;
+  grpc_error* error_list[2];
+  int error_count = 0;
+  grpc_error* global_error = ParseGlobalParams(json_tree);
+  grpc_error* local_error = ParsePerMethodParams(json_tree);
+  if (global_error != GRPC_ERROR_NONE) {
+    error_list[error_count++] = global_error;
+  }
+  if (local_error != GRPC_ERROR_NONE) {
+    error_list[error_count++] = local_error;
+  }
+  if (error_count > 0) {
+    *error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
+        "Service config parsing error", error_list, error_count);
+    GRPC_ERROR_UNREF(global_error);
+    GRPC_ERROR_UNREF(local_error);
+  }
+}
+
+grpc_error* ServiceConfig::ParseGlobalParams(const grpc_json* json_tree) {
+  GPR_DEBUG_ASSERT(json_tree_->type == GRPC_JSON_OBJECT);
+  GPR_DEBUG_ASSERT(json_tree_->key == nullptr);
+  InlinedVector<grpc_error*, 4> error_list;
+  for (size_t i = 0; i < g_registered_parsers->size(); i++) {
+    grpc_error* parser_error = GRPC_ERROR_NONE;
+    auto parsed_obj =
+        (*g_registered_parsers)[i]->ParseGlobalParams(json_tree, &parser_error);
+    if (parser_error != GRPC_ERROR_NONE) {
+      error_list.push_back(parser_error);
+    }
+    parsed_global_service_config_objects_.push_back(std::move(parsed_obj));
+  }
+  return GRPC_ERROR_CREATE_FROM_VECTOR("Global Params", &error_list);
+}
+
+grpc_error* ServiceConfig::ParseJsonMethodConfigToServiceConfigObjectsTable(
+    const grpc_json* json,
+    SliceHashTable<const ServiceConfigObjectsVector*>::Entry* entries,
+    size_t* idx) {
+  auto objs_vector = MakeUnique<ServiceConfigObjectsVector>();
+  InlinedVector<grpc_error*, 4> error_list;
+  for (size_t i = 0; i < g_registered_parsers->size(); i++) {
+    grpc_error* parser_error = GRPC_ERROR_NONE;
+    auto parsed_obj =
+        (*g_registered_parsers)[i]->ParsePerMethodParams(json, &parser_error);
+    if (parser_error != GRPC_ERROR_NONE) {
+      error_list.push_back(parser_error);
+    }
+    objs_vector->push_back(std::move(parsed_obj));
+  }
+  service_config_objects_vectors_storage_.push_back(std::move(objs_vector));
+  const auto* vector_ptr =
+      service_config_objects_vectors_storage_
+          [service_config_objects_vectors_storage_.size() - 1]
+              .get();
+  // Construct list of paths.
+  InlinedVector<UniquePtr<char>, 10> paths;
+  for (grpc_json* child = json->child; child != nullptr; child = child->next) {
+    if (child->key == nullptr) continue;
+    if (strcmp(child->key, "name") == 0) {
+      if (child->type != GRPC_JSON_ARRAY) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:name error:not of type Array"));
+        goto wrap_error;
+      }
+      for (grpc_json* name = child->child; name != nullptr; name = name->next) {
+        grpc_error* parse_error = GRPC_ERROR_NONE;
+        UniquePtr<char> path = ParseJsonMethodName(name, &parse_error);
+        if (path == nullptr) {
+          error_list.push_back(parse_error);
+        } else {
+          GPR_DEBUG_ASSERT(parse_error == GRPC_ERROR_NONE);
+          paths.push_back(std::move(path));
+        }
+      }
     }
   }
-  return lb_policy_name;
+  if (paths.size() == 0) {
+    error_list.push_back(
+        GRPC_ERROR_CREATE_FROM_STATIC_STRING("No names specified"));
+  }
+  // Add entry for each path.
+  for (size_t i = 0; i < paths.size(); ++i) {
+    entries[*idx].key = grpc_slice_from_copied_string(paths[i].get());
+    entries[*idx].value = vector_ptr;
+    ++*idx;
+  }
+wrap_error:
+  return GRPC_ERROR_CREATE_FROM_VECTOR("methodConfig", &error_list);
 }
 
+grpc_error* ServiceConfig::ParsePerMethodParams(const grpc_json* json_tree) {
+  GPR_DEBUG_ASSERT(json_tree_->type == GRPC_JSON_OBJECT);
+  GPR_DEBUG_ASSERT(json_tree_->key == nullptr);
+  SliceHashTable<const ServiceConfigObjectsVector*>::Entry* entries = nullptr;
+  size_t num_entries = 0;
+  InlinedVector<grpc_error*, 4> error_list;
+  for (grpc_json* field = json_tree->child; field != nullptr;
+       field = field->next) {
+    if (field->key == nullptr) {
+      error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "error:Illegal key value - NULL"));
+      continue;
+    }
+    if (strcmp(field->key, "methodConfig") == 0) {
+      if (entries != nullptr) {
+        GPR_ASSERT(false);
+      }
+      if (field->type != GRPC_JSON_ARRAY) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:methodConfig error:not of type Array"));
+      }
+      for (grpc_json* method = field->child; method != nullptr;
+           method = method->next) {
+        int count = CountNamesInMethodConfig(method);
+        if (count <= 0) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:methodConfig error:No names found"));
+        }
+        num_entries += static_cast<size_t>(count);
+      }
+      entries = static_cast<
+          SliceHashTable<const ServiceConfigObjectsVector*>::Entry*>(gpr_zalloc(
+          num_entries *
+          sizeof(SliceHashTable<const ServiceConfigObjectsVector*>::Entry)));
+      size_t idx = 0;
+      for (grpc_json* method = field->child; method != nullptr;
+           method = method->next) {
+        grpc_error* error = ParseJsonMethodConfigToServiceConfigObjectsTable(
+            method, entries, &idx);
+        if (error != GRPC_ERROR_NONE) {
+          error_list.push_back(error);
+        }
+      }
+      // idx might not be equal to num_entries due to parsing errors
+      num_entries = idx;
+      break;
+    }
+  }
+  if (entries != nullptr) {
+    parsed_method_service_config_objects_table_ =
+        SliceHashTable<const ServiceConfigObjectsVector*>::Create(
+            num_entries, entries, nullptr);
+    gpr_free(entries);
+  }
+  return GRPC_ERROR_CREATE_FROM_VECTOR("Method Params", &error_list);
+}
+
+ServiceConfig::~ServiceConfig() { grpc_json_destroy(json_tree_); }
+
 int ServiceConfig::CountNamesInMethodConfig(grpc_json* json) {
   int num_names = 0;
   for (grpc_json* field = json->child; field != nullptr; field = field->next) {
@@ -84,28 +230,102 @@
   return num_names;
 }
 
-UniquePtr<char> ServiceConfig::ParseJsonMethodName(grpc_json* json) {
-  if (json->type != GRPC_JSON_OBJECT) return nullptr;
+UniquePtr<char> ServiceConfig::ParseJsonMethodName(grpc_json* json,
+                                                   grpc_error** error) {
+  if (json->type != GRPC_JSON_OBJECT) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "field:name error:type is not object");
+    return nullptr;
+  }
   const char* service_name = nullptr;
   const char* method_name = nullptr;
   for (grpc_json* child = json->child; child != nullptr; child = child->next) {
-    if (child->key == nullptr) return nullptr;
-    if (child->type != GRPC_JSON_STRING) return nullptr;
+    if (child->key == nullptr) {
+      *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "field:name error:Child entry with no key");
+      return nullptr;
+    }
+    if (child->type != GRPC_JSON_STRING) {
+      *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+          "field:name error:Child entry not of type string");
+      return nullptr;
+    }
     if (strcmp(child->key, "service") == 0) {
-      if (service_name != nullptr) return nullptr;  // Duplicate.
-      if (child->value == nullptr) return nullptr;
+      if (service_name != nullptr) {
+        *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:name error: field:service error:Multiple entries");
+        return nullptr;  // Duplicate.
+      }
+      if (child->value == nullptr) {
+        *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:name error: field:service error:empty value");
+        return nullptr;
+      }
       service_name = child->value;
     } else if (strcmp(child->key, "method") == 0) {
-      if (method_name != nullptr) return nullptr;  // Duplicate.
-      if (child->value == nullptr) return nullptr;
+      if (method_name != nullptr) {
+        *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:name error: field:method error:multiple entries");
+        return nullptr;  // Duplicate.
+      }
+      if (child->value == nullptr) {
+        *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:name error: field:method error:empty value");
+        return nullptr;
+      }
       method_name = child->value;
     }
   }
-  if (service_name == nullptr) return nullptr;  // Required field.
+  if (service_name == nullptr) {
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "field:name error: field:service error:not found");
+    return nullptr;  // Required field.
+  }
   char* path;
   gpr_asprintf(&path, "/%s/%s", service_name,
                method_name == nullptr ? "*" : method_name);
   return UniquePtr<char>(path);
 }
 
+const ServiceConfig::ServiceConfigObjectsVector*
+ServiceConfig::GetMethodServiceConfigObjectsVector(const grpc_slice& path) {
+  if (parsed_method_service_config_objects_table_.get() == nullptr) {
+    return nullptr;
+  }
+  const auto* value = parsed_method_service_config_objects_table_->Get(path);
+  // If we didn't find a match for the path, try looking for a wildcard
+  // entry (i.e., change "/service/method" to "/service/*").
+  if (value == nullptr) {
+    char* path_str = grpc_slice_to_c_string(path);
+    const char* sep = strrchr(path_str, '/') + 1;
+    const size_t len = (size_t)(sep - path_str);
+    char* buf = (char*)gpr_malloc(len + 2);  // '*' and NUL
+    memcpy(buf, path_str, len);
+    buf[len] = '*';
+    buf[len + 1] = '\0';
+    grpc_slice wildcard_path = grpc_slice_from_copied_string(buf);
+    gpr_free(buf);
+    value = parsed_method_service_config_objects_table_->Get(wildcard_path);
+    grpc_slice_unref_internal(wildcard_path);
+    gpr_free(path_str);
+    if (value == nullptr) return nullptr;
+  }
+  return *value;
+}
+
+size_t ServiceConfig::RegisterParser(UniquePtr<Parser> parser) {
+  g_registered_parsers->push_back(std::move(parser));
+  return g_registered_parsers->size() - 1;
+}
+
+void ServiceConfig::Init() {
+  GPR_ASSERT(g_registered_parsers == nullptr);
+  g_registered_parsers = New<ServiceConfigParserList>();
+}
+
+void ServiceConfig::Shutdown() {
+  Delete(g_registered_parsers);
+  g_registered_parsers = nullptr;
+}
+
 }  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/service_config.h b/src/core/ext/filters/client_channel/service_config.h
index d906347..e6f855a 100644
--- a/src/core/ext/filters/client_channel/service_config.h
+++ b/src/core/ext/filters/client_channel/service_config.h
@@ -25,6 +25,7 @@
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/iomgr/error.h"
 #include "src/core/lib/json/json.h"
 #include "src/core/lib/slice/slice_hash_table.h"
 
@@ -56,45 +57,106 @@
 
 class ServiceConfig : public RefCounted<ServiceConfig> {
  public:
+  /// This is the base class that all service config parsers MUST use to store
+  /// parsed service config data.
+  class ParsedConfig {
+   public:
+    virtual ~ParsedConfig() = default;
+
+    GRPC_ABSTRACT_BASE_CLASS;
+  };
+
+  /// This is the base class that all service config parsers should derive from.
+  class Parser {
+   public:
+    virtual ~Parser() = default;
+
+    virtual UniquePtr<ParsedConfig> ParseGlobalParams(const grpc_json* json,
+                                                      grpc_error** error) {
+      GPR_DEBUG_ASSERT(error != nullptr);
+      return nullptr;
+    }
+
+    virtual UniquePtr<ParsedConfig> ParsePerMethodParams(const grpc_json* json,
+                                                         grpc_error** error) {
+      GPR_DEBUG_ASSERT(error != nullptr);
+      return nullptr;
+    }
+
+    GRPC_ABSTRACT_BASE_CLASS;
+  };
+
+  static constexpr int kNumPreallocatedParsers = 4;
+  typedef InlinedVector<UniquePtr<ParsedConfig>, kNumPreallocatedParsers>
+      ServiceConfigObjectsVector;
+
+  /// When a service config is applied to a call in the client_channel_filter,
+  /// we create an instance of this object and store it in the call_data for
+  /// client_channel. A pointer to this object is also stored in the
+  /// call_context, so that future filters can easily access method and global
+  /// parameters for the call.
+  class CallData {
+   public:
+    CallData() = default;
+    CallData(RefCountedPtr<ServiceConfig> svc_cfg, const grpc_slice& path)
+        : service_config_(std::move(svc_cfg)) {
+      if (service_config_ != nullptr) {
+        method_params_vector_ =
+            service_config_->GetMethodServiceConfigObjectsVector(path);
+      }
+    }
+
+    ServiceConfig* service_config() { return service_config_.get(); }
+
+    ParsedConfig* GetMethodParsedObject(size_t index) const {
+      return method_params_vector_ != nullptr
+                 ? (*method_params_vector_)[index].get()
+                 : nullptr;
+    }
+
+    ParsedConfig* GetGlobalParsedObject(size_t index) const {
+      return service_config_->GetParsedGlobalServiceConfigObject(index);
+    }
+
+   private:
+    RefCountedPtr<ServiceConfig> service_config_;
+    const ServiceConfigObjectsVector* method_params_vector_ = nullptr;
+  };
+
   /// Creates a new service config from parsing \a json_string.
   /// Returns null on parse error.
-  static RefCountedPtr<ServiceConfig> Create(const char* json);
+  static RefCountedPtr<ServiceConfig> Create(const char* json,
+                                             grpc_error** error);
 
   ~ServiceConfig();
 
   const char* service_config_json() const { return service_config_json_.get(); }
 
-  /// Invokes \a process_json() for each global parameter in the service
-  /// config.  \a arg is passed as the second argument to \a process_json().
-  template <typename T>
-  using ProcessJson = void (*)(const grpc_json*, T*);
-  template <typename T>
-  void ParseGlobalParams(ProcessJson<T> process_json, T* arg) const;
+  /// Retrieves the parsed global service config object at index \a index. The
+  /// lifetime of the returned object is tied to the lifetime of the
+  /// ServiceConfig object.
+  ParsedConfig* GetParsedGlobalServiceConfigObject(size_t index) {
+    GPR_DEBUG_ASSERT(index < parsed_global_service_config_objects_.size());
+    return parsed_global_service_config_objects_[index].get();
+  }
 
-  /// Gets the LB policy name from \a service_config.
-  /// Returns NULL if no LB policy name was specified.
-  /// Caller does NOT take ownership.
-  const char* GetLoadBalancingPolicyName() const;
+  /// Retrieves the vector of method service config objects for a given path \a
+  /// path. The lifetime of the returned vector and contained objects is tied to
+  /// the lifetime of the ServiceConfig object.
+  const ServiceConfigObjectsVector* GetMethodServiceConfigObjectsVector(
+      const grpc_slice& path);
 
-  /// Creates a method config table based on the data in \a json.
-  /// The table's keys are request paths.  The table's value type is
-  /// returned by \a create_value(), based on data parsed from the JSON tree.
-  /// Returns null on error.
-  template <typename T>
-  using CreateValue = RefCountedPtr<T> (*)(const grpc_json* method_config_json);
-  template <typename T>
-  RefCountedPtr<SliceHashTable<RefCountedPtr<T>>> CreateMethodConfigTable(
-      CreateValue<T> create_value) const;
+  /// Globally register a service config parser. On successful registration, it
+  /// returns the index at which the parser was registered. On failure, -1 is
+  /// returned. Each new service config update will go through all the
+  /// registered parser. Each parser is responsible for reading the service
+  /// config json and returning a parsed object. This parsed object can later be
+  /// retrieved using the same index that was returned at registration time.
+  static size_t RegisterParser(UniquePtr<Parser> parser);
 
-  /// A helper function for looking up values in the table returned by
-  /// \a CreateMethodConfigTable().
-  /// Gets the method config for the specified \a path, which should be of
-  /// the form "/service/method".
-  /// Returns null if the method has no config.
-  /// Caller does NOT own a reference to the result.
-  template <typename T>
-  static RefCountedPtr<T> MethodConfigTableLookup(
-      const SliceHashTable<RefCountedPtr<T>>& table, const grpc_slice& path);
+  static void Init();
+
+  static void Shutdown();
 
  private:
   // So New() can call our private ctor.
@@ -103,152 +165,43 @@
 
   // Takes ownership of \a json_tree.
   ServiceConfig(UniquePtr<char> service_config_json,
-                UniquePtr<char> json_string, grpc_json* json_tree);
+                UniquePtr<char> json_string, grpc_json* json_tree,
+                grpc_error** error);
+
+  // Helper functions to parse the service config
+  grpc_error* ParseGlobalParams(const grpc_json* json_tree);
+  grpc_error* ParsePerMethodParams(const grpc_json* json_tree);
 
   // Returns the number of names specified in the method config \a json.
   static int CountNamesInMethodConfig(grpc_json* json);
 
   // Returns a path string for the JSON name object specified by \a json.
-  // Returns null on error.
-  static UniquePtr<char> ParseJsonMethodName(grpc_json* json);
+  // Returns null on error, and stores error in \a error.
+  static UniquePtr<char> ParseJsonMethodName(grpc_json* json,
+                                             grpc_error** error);
 
-  // Parses the method config from \a json.  Adds an entry to \a entries for
-  // each name found, incrementing \a idx for each entry added.
-  // Returns false on error.
-  template <typename T>
-  static bool ParseJsonMethodConfig(
-      grpc_json* json, CreateValue<T> create_value,
-      typename SliceHashTable<RefCountedPtr<T>>::Entry* entries, size_t* idx);
+  grpc_error* ParseJsonMethodConfigToServiceConfigObjectsTable(
+      const grpc_json* json,
+      SliceHashTable<const ServiceConfigObjectsVector*>::Entry* entries,
+      size_t* idx);
 
   UniquePtr<char> service_config_json_;
   UniquePtr<char> json_string_;  // Underlying storage for json_tree.
   grpc_json* json_tree_;
+
+  InlinedVector<UniquePtr<ParsedConfig>, kNumPreallocatedParsers>
+      parsed_global_service_config_objects_;
+  // A map from the method name to the service config objects vector. Note that
+  // we are using a raw pointer and not a unique pointer so that we can use the
+  // same vector for multiple names.
+  RefCountedPtr<SliceHashTable<const ServiceConfigObjectsVector*>>
+      parsed_method_service_config_objects_table_;
+  // Storage for all the vectors that are being used in
+  // parsed_method_service_config_objects_table_.
+  InlinedVector<UniquePtr<ServiceConfigObjectsVector>, 32>
+      service_config_objects_vectors_storage_;
 };
 
-//
-// implementation -- no user-serviceable parts below
-//
-
-template <typename T>
-void ServiceConfig::ParseGlobalParams(ProcessJson<T> process_json,
-                                      T* arg) const {
-  if (json_tree_->type != GRPC_JSON_OBJECT || json_tree_->key != nullptr) {
-    return;
-  }
-  for (grpc_json* field = json_tree_->child; field != nullptr;
-       field = field->next) {
-    if (field->key == nullptr) return;
-    if (strcmp(field->key, "methodConfig") == 0) continue;
-    process_json(field, arg);
-  }
-}
-
-template <typename T>
-bool ServiceConfig::ParseJsonMethodConfig(
-    grpc_json* json, CreateValue<T> create_value,
-    typename SliceHashTable<RefCountedPtr<T>>::Entry* entries, size_t* idx) {
-  // Construct value.
-  RefCountedPtr<T> method_config = create_value(json);
-  if (method_config == nullptr) return false;
-  // Construct list of paths.
-  InlinedVector<UniquePtr<char>, 10> paths;
-  for (grpc_json* child = json->child; child != nullptr; child = child->next) {
-    if (child->key == nullptr) continue;
-    if (strcmp(child->key, "name") == 0) {
-      if (child->type != GRPC_JSON_ARRAY) return false;
-      for (grpc_json* name = child->child; name != nullptr; name = name->next) {
-        UniquePtr<char> path = ParseJsonMethodName(name);
-        if (path == nullptr) return false;
-        paths.push_back(std::move(path));
-      }
-    }
-  }
-  if (paths.size() == 0) return false;  // No names specified.
-  // Add entry for each path.
-  for (size_t i = 0; i < paths.size(); ++i) {
-    entries[*idx].key = grpc_slice_from_copied_string(paths[i].get());
-    entries[*idx].value = method_config;  // Takes a new ref.
-    ++*idx;
-  }
-  // Success.
-  return true;
-}
-
-template <typename T>
-RefCountedPtr<SliceHashTable<RefCountedPtr<T>>>
-ServiceConfig::CreateMethodConfigTable(CreateValue<T> create_value) const {
-  // Traverse parsed JSON tree.
-  if (json_tree_->type != GRPC_JSON_OBJECT || json_tree_->key != nullptr) {
-    return nullptr;
-  }
-  size_t num_entries = 0;
-  typename SliceHashTable<RefCountedPtr<T>>::Entry* entries = nullptr;
-  for (grpc_json* field = json_tree_->child; field != nullptr;
-       field = field->next) {
-    if (field->key == nullptr) return nullptr;
-    if (strcmp(field->key, "methodConfig") == 0) {
-      if (entries != nullptr) return nullptr;  // Duplicate.
-      if (field->type != GRPC_JSON_ARRAY) return nullptr;
-      // Find number of entries.
-      for (grpc_json* method = field->child; method != nullptr;
-           method = method->next) {
-        int count = CountNamesInMethodConfig(method);
-        if (count <= 0) return nullptr;
-        num_entries += static_cast<size_t>(count);
-      }
-      // Populate method config table entries.
-      entries = static_cast<typename SliceHashTable<RefCountedPtr<T>>::Entry*>(
-          gpr_zalloc(num_entries *
-                     sizeof(typename SliceHashTable<RefCountedPtr<T>>::Entry)));
-      size_t idx = 0;
-      for (grpc_json* method = field->child; method != nullptr;
-           method = method->next) {
-        if (!ParseJsonMethodConfig(method, create_value, entries, &idx)) {
-          for (size_t i = 0; i < idx; ++i) {
-            grpc_slice_unref_internal(entries[i].key);
-            entries[i].value.reset();
-          }
-          gpr_free(entries);
-          return nullptr;
-        }
-      }
-      GPR_ASSERT(idx == num_entries);
-    }
-  }
-  // Instantiate method config table.
-  RefCountedPtr<SliceHashTable<RefCountedPtr<T>>> method_config_table;
-  if (entries != nullptr) {
-    method_config_table =
-        SliceHashTable<RefCountedPtr<T>>::Create(num_entries, entries, nullptr);
-    gpr_free(entries);
-  }
-  return method_config_table;
-}
-
-template <typename T>
-RefCountedPtr<T> ServiceConfig::MethodConfigTableLookup(
-    const SliceHashTable<RefCountedPtr<T>>& table, const grpc_slice& path) {
-  const RefCountedPtr<T>* value = table.Get(path);
-  // If we didn't find a match for the path, try looking for a wildcard
-  // entry (i.e., change "/service/method" to "/service/*").
-  if (value == nullptr) {
-    char* path_str = grpc_slice_to_c_string(path);
-    const char* sep = strrchr(path_str, '/') + 1;
-    const size_t len = (size_t)(sep - path_str);
-    char* buf = (char*)gpr_malloc(len + 2);  // '*' and NUL
-    memcpy(buf, path_str, len);
-    buf[len] = '*';
-    buf[len + 1] = '\0';
-    grpc_slice wildcard_path = grpc_slice_from_copied_string(buf);
-    gpr_free(buf);
-    value = table.Get(wildcard_path);
-    grpc_slice_unref_internal(wildcard_path);
-    gpr_free(path_str);
-    if (value == nullptr) return nullptr;
-  }
-  return RefCountedPtr<T>(*value);
-}
-
 }  // namespace grpc_core
 
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_SERVICE_CONFIG_H */
diff --git a/src/core/ext/filters/client_channel/subchannel.cc b/src/core/ext/filters/client_channel/subchannel.cc
index 8bb0c4c..a284e69 100644
--- a/src/core/ext/filters/client_channel/subchannel.cc
+++ b/src/core/ext/filters/client_channel/subchannel.cc
@@ -42,8 +42,8 @@
 #include "src/core/lib/gpr/alloc.h"
 #include "src/core/lib/gprpp/debug_location.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
 #include "src/core/lib/profiling/timers.h"
 #include "src/core/lib/slice/slice_internal.h"
@@ -121,7 +121,7 @@
   const size_t allocation_size =
       GetInitialCallSizeEstimate(args.parent_data_size);
   RefCountedPtr<SubchannelCall> call(
-      new (gpr_arena_alloc(args.arena, allocation_size))
+      new (args.arena->Alloc(allocation_size))
           SubchannelCall(Ref(DEBUG_LOCATION, "subchannel_call"), args));
   grpc_call_stack* callstk = SUBCHANNEL_CALL_TO_CALL_STACK(call.get());
   const grpc_call_element_args call_args = {
@@ -332,10 +332,9 @@
       health_state = GRPC_CHANNEL_CONNECTING;
     }
     // Report initial state.
-    c->SetConnectivityStateLocked(GRPC_CHANNEL_READY, GRPC_ERROR_NONE,
-                                  "subchannel_connected");
+    c->SetConnectivityStateLocked(GRPC_CHANNEL_READY, "subchannel_connected");
     grpc_connectivity_state_set(&c->state_and_health_tracker_, health_state,
-                                GRPC_ERROR_NONE, "subchannel_connected");
+                                "subchannel_connected");
   }
 
   ~ConnectedSubchannelStateWatcher() {
@@ -367,11 +366,10 @@
             c->connected_subchannel_watcher_.reset();
             self->last_connectivity_state_ = GRPC_CHANNEL_TRANSIENT_FAILURE;
             c->SetConnectivityStateLocked(GRPC_CHANNEL_TRANSIENT_FAILURE,
-                                          GRPC_ERROR_REF(error),
                                           "reflect_child");
             grpc_connectivity_state_set(&c->state_and_health_tracker_,
                                         GRPC_CHANNEL_TRANSIENT_FAILURE,
-                                        GRPC_ERROR_REF(error), "reflect_child");
+                                        "reflect_child");
             c->backoff_begun_ = false;
             c->backoff_.Reset();
             c->MaybeStartConnectingLocked();
@@ -388,11 +386,11 @@
           // from READY to CONNECTING or IDLE.
           self->last_connectivity_state_ = self->pending_connectivity_state_;
           c->SetConnectivityStateLocked(self->pending_connectivity_state_,
-                                        GRPC_ERROR_REF(error), "reflect_child");
+                                        "reflect_child");
           if (self->pending_connectivity_state_ != GRPC_CHANNEL_READY) {
             grpc_connectivity_state_set(&c->state_and_health_tracker_,
                                         self->pending_connectivity_state_,
-                                        GRPC_ERROR_REF(error), "reflect_child");
+                                        "reflect_child");
           }
           c->connected_subchannel_->NotifyOnStateChange(
               nullptr, &self->pending_connectivity_state_,
@@ -415,8 +413,7 @@
           self->health_check_client_ != nullptr) {
         if (self->last_connectivity_state_ == GRPC_CHANNEL_READY) {
           grpc_connectivity_state_set(&c->state_and_health_tracker_,
-                                      self->health_state_,
-                                      GRPC_ERROR_REF(error), "health_changed");
+                                      self->health_state_, "health_changed");
         }
         self->health_check_client_->NotifyOnHealthChange(
             &self->health_state_, &self->on_health_changed_);
@@ -457,13 +454,14 @@
       grpc_pollset_set_del_pollset_set(w->subchannel->pollset_set_,
                                        w->pollset_set);
     }
-    gpr_mu_lock(&w->subchannel->mu_);
-    if (w->subchannel->external_state_watcher_list_ == w) {
-      w->subchannel->external_state_watcher_list_ = w->next;
+    {
+      MutexLock lock(&w->subchannel->mu_);
+      if (w->subchannel->external_state_watcher_list_ == w) {
+        w->subchannel->external_state_watcher_list_ = w->next;
+      }
+      if (w->next != nullptr) w->next->prev = w->prev;
+      if (w->prev != nullptr) w->prev->next = w->next;
     }
-    if (w->next != nullptr) w->next->prev = w->prev;
-    if (w->prev != nullptr) w->prev->next = w->next;
-    gpr_mu_unlock(&w->subchannel->mu_);
     GRPC_SUBCHANNEL_WEAK_UNREF(w->subchannel, "external_state_watcher+done");
     Delete(w);
     GRPC_CLOSURE_SCHED(follow_up, GRPC_ERROR_REF(error));
@@ -531,25 +529,6 @@
       .set_max_backoff(max_backoff_ms);
 }
 
-struct HealthCheckParams {
-  UniquePtr<char> service_name;
-
-  static void Parse(const grpc_json* field, HealthCheckParams* params) {
-    if (strcmp(field->key, "healthCheckConfig") == 0) {
-      if (field->type != GRPC_JSON_OBJECT) return;
-      for (grpc_json* sub_field = field->child; sub_field != nullptr;
-           sub_field = sub_field->next) {
-        if (sub_field->key == nullptr) return;
-        if (strcmp(sub_field->key, "serviceName") == 0) {
-          if (params->service_name != nullptr) return;  // Duplicate.
-          if (sub_field->type != GRPC_JSON_STRING) return;
-          params->service_name.reset(gpr_strdup(sub_field->value));
-        }
-      }
-    }
-  }
-};
-
 }  // namespace
 
 Subchannel::Subchannel(SubchannelKey* key, grpc_connector* connector,
@@ -585,19 +564,9 @@
                                "subchannel");
   grpc_connectivity_state_init(&state_and_health_tracker_, GRPC_CHANNEL_IDLE,
                                "subchannel");
-  gpr_mu_init(&mu_);
-  // Check whether we should enable health checking.
-  const char* service_config_json = grpc_channel_arg_get_string(
-      grpc_channel_args_find(args_, GRPC_ARG_SERVICE_CONFIG));
-  if (service_config_json != nullptr) {
-    RefCountedPtr<ServiceConfig> service_config =
-        ServiceConfig::Create(service_config_json);
-    if (service_config != nullptr) {
-      HealthCheckParams params;
-      service_config->ParseGlobalParams(HealthCheckParams::Parse, &params);
-      health_check_service_name_ = std::move(params.service_name);
-    }
-  }
+  health_check_service_name_ =
+      UniquePtr<char>(gpr_strdup(grpc_channel_arg_get_string(
+          grpc_channel_args_find(args_, "grpc.temp.health_check"))));
   const grpc_arg* arg = grpc_channel_args_find(args_, GRPC_ARG_ENABLE_CHANNELZ);
   const bool channelz_enabled =
       grpc_channel_arg_get_bool(arg, GRPC_ENABLE_CHANNELZ_DEFAULT);
@@ -629,7 +598,6 @@
   grpc_connector_unref(connector_);
   grpc_pollset_set_destroy(pollset_set_);
   Delete(key_);
-  gpr_mu_destroy(&mu_);
 }
 
 Subchannel* Subchannel::Create(grpc_connector* connector,
@@ -740,11 +708,10 @@
 }
 
 grpc_connectivity_state Subchannel::CheckConnectivity(
-    grpc_error** error, bool inhibit_health_checking) {
-  MutexLock lock(&mu_);
+    bool inhibit_health_checking) {
   grpc_connectivity_state_tracker* tracker =
       inhibit_health_checking ? &state_tracker_ : &state_and_health_tracker_;
-  grpc_connectivity_state state = grpc_connectivity_state_get(tracker, error);
+  grpc_connectivity_state state = grpc_connectivity_state_check(tracker);
   return state;
 }
 
@@ -852,7 +819,6 @@
 }  // namespace
 
 void Subchannel::SetConnectivityStateLocked(grpc_connectivity_state state,
-                                            grpc_error* error,
                                             const char* reason) {
   if (channelz_node_ != nullptr) {
     channelz_node_->AddTraceEvent(
@@ -860,7 +826,7 @@
         grpc_slice_from_static_string(
             SubchannelConnectivityStateChangeString(state)));
   }
-  grpc_connectivity_state_set(&state_tracker_, state, error, reason);
+  grpc_connectivity_state_set(&state_tracker_, state, reason);
 }
 
 void Subchannel::MaybeStartConnectingLocked() {
@@ -905,7 +871,9 @@
 
 void Subchannel::OnRetryAlarm(void* arg, grpc_error* error) {
   Subchannel* c = static_cast<Subchannel*>(arg);
-  gpr_mu_lock(&c->mu_);
+  // TODO(soheilhy): Once subchannel refcounting is simplified, we can get use
+  //                 MutexLock instead of ReleasableMutexLock, here.
+  ReleasableMutexLock lock(&c->mu_);
   c->have_retry_alarm_ = false;
   if (c->disconnected_) {
     error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING("Disconnected",
@@ -919,9 +887,9 @@
   if (error == GRPC_ERROR_NONE) {
     gpr_log(GPR_INFO, "Failed to connect to channel, retrying");
     c->ContinueConnectingLocked();
-    gpr_mu_unlock(&c->mu_);
+    lock.Unlock();
   } else {
-    gpr_mu_unlock(&c->mu_);
+    lock.Unlock();
     GRPC_SUBCHANNEL_WEAK_UNREF(c, "connecting");
   }
   GRPC_ERROR_UNREF(error);
@@ -935,11 +903,9 @@
   next_attempt_deadline_ = backoff_.NextAttemptTime();
   args.deadline = std::max(next_attempt_deadline_, min_deadline);
   args.channel_args = args_;
-  SetConnectivityStateLocked(GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE,
-                             "connecting");
+  SetConnectivityStateLocked(GRPC_CHANNEL_CONNECTING, "connecting");
   grpc_connectivity_state_set(&state_and_health_tracker_,
-                              GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE,
-                              "connecting");
+                              GRPC_CHANNEL_CONNECTING, "connecting");
   grpc_connector_connect(connector_, &args, &connecting_result_,
                          &on_connecting_finished_);
 }
@@ -948,29 +914,25 @@
   auto* c = static_cast<Subchannel*>(arg);
   grpc_channel_args* delete_channel_args = c->connecting_result_.channel_args;
   GRPC_SUBCHANNEL_WEAK_REF(c, "on_connecting_finished");
-  gpr_mu_lock(&c->mu_);
-  c->connecting_ = false;
-  if (c->connecting_result_.transport != nullptr &&
-      c->PublishTransportLocked()) {
-    // Do nothing, transport was published.
-  } else if (c->disconnected_) {
-    GRPC_SUBCHANNEL_WEAK_UNREF(c, "connecting");
-  } else {
-    const char* errmsg = grpc_error_string(error);
-    gpr_log(GPR_INFO, "Connect failed: %s", errmsg);
-    error =
-        grpc_error_set_int(GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
-                               "Connect Failed", &error, 1),
-                           GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE);
-    c->SetConnectivityStateLocked(GRPC_CHANNEL_TRANSIENT_FAILURE,
-                                  GRPC_ERROR_REF(error), "connect_failed");
-    grpc_connectivity_state_set(&c->state_and_health_tracker_,
-                                GRPC_CHANNEL_TRANSIENT_FAILURE, error,
-                                "connect_failed");
-    c->MaybeStartConnectingLocked();
-    GRPC_SUBCHANNEL_WEAK_UNREF(c, "connecting");
+  {
+    MutexLock lock(&c->mu_);
+    c->connecting_ = false;
+    if (c->connecting_result_.transport != nullptr &&
+        c->PublishTransportLocked()) {
+      // Do nothing, transport was published.
+    } else if (c->disconnected_) {
+      GRPC_SUBCHANNEL_WEAK_UNREF(c, "connecting");
+    } else {
+      gpr_log(GPR_INFO, "Connect failed: %s", grpc_error_string(error));
+      c->SetConnectivityStateLocked(GRPC_CHANNEL_TRANSIENT_FAILURE,
+                                    "connect_failed");
+      grpc_connectivity_state_set(&c->state_and_health_tracker_,
+                                  GRPC_CHANNEL_TRANSIENT_FAILURE,
+                                  "connect_failed");
+      c->MaybeStartConnectingLocked();
+      GRPC_SUBCHANNEL_WEAK_UNREF(c, "connecting");
+    }
   }
-  gpr_mu_unlock(&c->mu_);
   GRPC_SUBCHANNEL_WEAK_UNREF(c, "on_connecting_finished");
   grpc_channel_args_destroy(delete_channel_args);
 }
diff --git a/src/core/ext/filters/client_channel/subchannel.h b/src/core/ext/filters/client_channel/subchannel.h
index 968fc74..e3efc89 100644
--- a/src/core/ext/filters/client_channel/subchannel.h
+++ b/src/core/ext/filters/client_channel/subchannel.h
@@ -26,9 +26,10 @@
 #include "src/core/ext/filters/client_channel/subchannel_pool_interface.h"
 #include "src/core/lib/backoff/backoff.h"
 #include "src/core/lib/channel/channel_stack.h"
-#include "src/core/lib/gpr/arena.h"
+#include "src/core/lib/gprpp/arena.h"
 #include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/polling_entity.h"
 #include "src/core/lib/iomgr/timer.h"
 #include "src/core/lib/transport/connectivity_state.h"
@@ -74,9 +75,9 @@
     grpc_slice path;
     gpr_timespec start_time;
     grpc_millis deadline;
-    gpr_arena* arena;
+    Arena* arena;
     grpc_call_context_element* context;
-    grpc_call_combiner* call_combiner;
+    grpc_core::CallCombiner* call_combiner;
     size_t parent_data_size;
   };
 
@@ -207,8 +208,7 @@
   channelz::SubchannelNode* channelz_node();
 
   // Polls the current connectivity state of the subchannel.
-  grpc_connectivity_state CheckConnectivity(grpc_error** error,
-                                            bool inhibit_health_checking);
+  grpc_connectivity_state CheckConnectivity(bool inhibit_health_checking);
 
   // When the connectivity state of the subchannel changes from \a *state,
   // invokes \a notify and updates \a *state with the new state.
@@ -241,7 +241,7 @@
 
   // Sets the subchannel's connectivity state to \a state.
   void SetConnectivityStateLocked(grpc_connectivity_state state,
-                                  grpc_error* error, const char* reason);
+                                  const char* reason);
 
   // Methods for connection.
   void MaybeStartConnectingLocked();
@@ -264,7 +264,7 @@
   // pollset_set tracking who's interested in a connection being setup.
   grpc_pollset_set* pollset_set_;
   // Protects the other members.
-  gpr_mu mu_;
+  Mutex mu_;
   // Refcount
   //    - lower INTERNAL_REF_BITS bits are for internal references:
   //      these do not keep the subchannel open.
diff --git a/src/core/ext/filters/deadline/deadline_filter.cc b/src/core/ext/filters/deadline/deadline_filter.cc
index b4cb07f..20a2953 100644
--- a/src/core/ext/filters/deadline/deadline_filter.cc
+++ b/src/core/ext/filters/deadline/deadline_filter.cc
@@ -68,8 +68,7 @@
     error = grpc_error_set_int(
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Deadline Exceeded"),
         GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_DEADLINE_EXCEEDED);
-    grpc_call_combiner_cancel(deadline_state->call_combiner,
-                              GRPC_ERROR_REF(error));
+    deadline_state->call_combiner->Cancel(GRPC_ERROR_REF(error));
     GRPC_CLOSURE_INIT(&deadline_state->timer_callback,
                       send_cancel_op_in_call_combiner, elem,
                       grpc_schedule_on_exec_ctx);
@@ -124,7 +123,7 @@
     deadline_state->timer_state = GRPC_DEADLINE_STATE_FINISHED;
     grpc_timer_cancel(&deadline_state->timer);
   } else {
-    // timer was either in STATE_INITAL (nothing to cancel)
+    // timer was either in STATE_INITIAL (nothing to cancel)
     // OR in STATE_FINISHED (again nothing to cancel)
   }
 }
@@ -183,7 +182,7 @@
 
 grpc_deadline_state::grpc_deadline_state(grpc_call_element* elem,
                                          grpc_call_stack* call_stack,
-                                         grpc_call_combiner* call_combiner,
+                                         grpc_core::CallCombiner* call_combiner,
                                          grpc_millis deadline)
     : call_stack(call_stack), call_combiner(call_combiner) {
   // Deadline will always be infinite on servers, so the timer will only be
diff --git a/src/core/ext/filters/deadline/deadline_filter.h b/src/core/ext/filters/deadline/deadline_filter.h
index e370329..7c4e9aa 100644
--- a/src/core/ext/filters/deadline/deadline_filter.h
+++ b/src/core/ext/filters/deadline/deadline_filter.h
@@ -32,12 +32,13 @@
 // Must be the first field in the filter's call_data.
 struct grpc_deadline_state {
   grpc_deadline_state(grpc_call_element* elem, grpc_call_stack* call_stack,
-                      grpc_call_combiner* call_combiner, grpc_millis deadline);
+                      grpc_core::CallCombiner* call_combiner,
+                      grpc_millis deadline);
   ~grpc_deadline_state();
 
   // We take a reference to the call stack for the timer callback.
   grpc_call_stack* call_stack;
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   grpc_deadline_timer_state timer_state = GRPC_DEADLINE_STATE_INITIAL;
   grpc_timer timer;
   grpc_closure timer_callback;
diff --git a/src/core/ext/filters/http/client/http_client_filter.cc b/src/core/ext/filters/http/client/http_client_filter.cc
index bf9a01f..4ef6c1f 100644
--- a/src/core/ext/filters/http/client/http_client_filter.cc
+++ b/src/core/ext/filters/http/client/http_client_filter.cc
@@ -36,7 +36,7 @@
 #define EXPECTED_CONTENT_TYPE "application/grpc"
 #define EXPECTED_CONTENT_TYPE_LENGTH sizeof(EXPECTED_CONTENT_TYPE) - 1
 
-/* default maximum size of payload eligable for GET request */
+/* default maximum size of payload eligible for GET request */
 static constexpr size_t kMaxPayloadSizeForGet = 2048;
 
 static void recv_initial_metadata_ready(void* user_data, grpc_error* error);
@@ -62,7 +62,7 @@
 
   ~call_data() { GRPC_ERROR_UNREF(recv_initial_metadata_error); }
 
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   // State for handling send_initial_metadata ops.
   grpc_linked_mdelem method;
   grpc_linked_mdelem scheme;
@@ -107,7 +107,8 @@
      * https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md.
      */
     if (b->idx.named.grpc_status != nullptr ||
-        grpc_mdelem_eq(b->idx.named.status->md, GRPC_MDELEM_STATUS_200)) {
+        grpc_mdelem_static_value_eq(b->idx.named.status->md,
+                                    GRPC_MDELEM_STATUS_200)) {
       grpc_metadata_batch_remove(b, b->idx.named.status);
     } else {
       char* val = grpc_dump_slice(GRPC_MDVALUE(b->idx.named.status->md),
@@ -140,8 +141,9 @@
   }
 
   if (b->idx.named.content_type != nullptr) {
-    if (!grpc_mdelem_eq(b->idx.named.content_type->md,
-                        GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC)) {
+    if (!grpc_mdelem_static_value_eq(
+            b->idx.named.content_type->md,
+            GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC)) {
       if (grpc_slice_buf_start_eq(GRPC_MDVALUE(b->idx.named.content_type->md),
                                   EXPECTED_CONTENT_TYPE,
                                   EXPECTED_CONTENT_TYPE_LENGTH) &&
diff --git a/src/core/ext/filters/http/client/http_client_filter.h b/src/core/ext/filters/http/client/http_client_filter.h
index b7cef33..a2f16dd 100644
--- a/src/core/ext/filters/http/client/http_client_filter.h
+++ b/src/core/ext/filters/http/client/http_client_filter.h
@@ -25,7 +25,7 @@
 /* Processes metadata on the client side for HTTP2 transports */
 extern const grpc_channel_filter grpc_http_client_filter;
 
-/* Channel arg to determine maximum size of payload eligable for GET request */
+/* Channel arg to determine maximum size of payload eligible for GET request */
 #define GRPC_ARG_MAX_PAYLOAD_SIZE_FOR_GET "grpc.max_payload_size_for_get"
 
 #endif /* GRPC_CORE_EXT_FILTERS_HTTP_CLIENT_HTTP_CLIENT_FILTER_H */
diff --git a/src/core/ext/filters/http/client_authority_filter.cc b/src/core/ext/filters/http/client_authority_filter.cc
index 125059c..85b30bc 100644
--- a/src/core/ext/filters/http/client_authority_filter.cc
+++ b/src/core/ext/filters/http/client_authority_filter.cc
@@ -40,7 +40,7 @@
 
 struct call_data {
   grpc_linked_mdelem authority_storage;
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
 };
 
 struct channel_data {
diff --git a/src/core/ext/filters/http/message_compress/message_compress_filter.cc b/src/core/ext/filters/http/message_compress/message_compress_filter.cc
index 9c8c8d9..d2b1f67 100644
--- a/src/core/ext/filters/http/message_compress/message_compress_filter.cc
+++ b/src/core/ext/filters/http/message_compress/message_compress_filter.cc
@@ -29,6 +29,7 @@
 #include "src/core/ext/filters/http/message_compress/message_compress_filter.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/compression/algorithm_metadata.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/compression/compression_internal.h"
 #include "src/core/lib/compression/message_compress.h"
 #include "src/core/lib/gpr/string.h"
@@ -71,7 +72,7 @@
     GRPC_ERROR_UNREF(cancel_error);
   }
 
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   grpc_linked_mdelem compression_algorithm_storage;
   grpc_linked_mdelem stream_compression_algorithm_storage;
   grpc_linked_mdelem accept_encoding_storage;
@@ -248,7 +249,7 @@
   bool did_compress = grpc_msg_compress(calld->message_compression_algorithm,
                                         &calld->slices, &tmp);
   if (did_compress) {
-    if (grpc_compression_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_compression_trace)) {
       const char* algo_name;
       const size_t before_size = calld->slices.length;
       const size_t after_size = tmp.length;
@@ -264,7 +265,7 @@
     grpc_slice_buffer_swap(&calld->slices, &tmp);
     send_flags |= GRPC_WRITE_INTERNAL_COMPRESS;
   } else {
-    if (grpc_compression_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_compression_trace)) {
       const char* algo_name;
       GPR_ASSERT(grpc_message_compression_algorithm_name(
           calld->message_compression_algorithm, &algo_name));
diff --git a/src/core/ext/filters/http/server/http_server_filter.cc b/src/core/ext/filters/http/server/http_server_filter.cc
index ce1be83..028d268 100644
--- a/src/core/ext/filters/http/server/http_server_filter.cc
+++ b/src/core/ext/filters/http/server/http_server_filter.cc
@@ -61,7 +61,7 @@
     }
   }
 
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
 
   // Outgoing headers to add to send_initial_metadata.
   grpc_linked_mdelem status;
@@ -131,18 +131,19 @@
   static const char* error_name = "Failed processing incoming headers";
 
   if (b->idx.named.method != nullptr) {
-    if (grpc_mdelem_eq(b->idx.named.method->md, GRPC_MDELEM_METHOD_POST)) {
+    if (grpc_mdelem_static_value_eq(b->idx.named.method->md,
+                                    GRPC_MDELEM_METHOD_POST)) {
       *calld->recv_initial_metadata_flags &=
           ~(GRPC_INITIAL_METADATA_CACHEABLE_REQUEST |
             GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST);
-    } else if (grpc_mdelem_eq(b->idx.named.method->md,
-                              GRPC_MDELEM_METHOD_PUT)) {
+    } else if (grpc_mdelem_static_value_eq(b->idx.named.method->md,
+                                           GRPC_MDELEM_METHOD_PUT)) {
       *calld->recv_initial_metadata_flags &=
           ~GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
       *calld->recv_initial_metadata_flags |=
           GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
-    } else if (grpc_mdelem_eq(b->idx.named.method->md,
-                              GRPC_MDELEM_METHOD_GET)) {
+    } else if (grpc_mdelem_static_value_eq(b->idx.named.method->md,
+                                           GRPC_MDELEM_METHOD_GET)) {
       *calld->recv_initial_metadata_flags |=
           GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
       *calld->recv_initial_metadata_flags &=
@@ -163,7 +164,8 @@
   }
 
   if (b->idx.named.te != nullptr) {
-    if (!grpc_mdelem_eq(b->idx.named.te->md, GRPC_MDELEM_TE_TRAILERS)) {
+    if (!grpc_mdelem_static_value_eq(b->idx.named.te->md,
+                                     GRPC_MDELEM_TE_TRAILERS)) {
       hs_add_error(error_name, &error,
                    grpc_attach_md_to_error(
                        GRPC_ERROR_CREATE_FROM_STATIC_STRING("Bad header"),
@@ -178,9 +180,12 @@
   }
 
   if (b->idx.named.scheme != nullptr) {
-    if (!grpc_mdelem_eq(b->idx.named.scheme->md, GRPC_MDELEM_SCHEME_HTTP) &&
-        !grpc_mdelem_eq(b->idx.named.scheme->md, GRPC_MDELEM_SCHEME_HTTPS) &&
-        !grpc_mdelem_eq(b->idx.named.scheme->md, GRPC_MDELEM_SCHEME_GRPC)) {
+    if (!grpc_mdelem_static_value_eq(b->idx.named.scheme->md,
+                                     GRPC_MDELEM_SCHEME_HTTP) &&
+        !grpc_mdelem_static_value_eq(b->idx.named.scheme->md,
+                                     GRPC_MDELEM_SCHEME_HTTPS) &&
+        !grpc_mdelem_static_value_eq(b->idx.named.scheme->md,
+                                     GRPC_MDELEM_SCHEME_GRPC)) {
       hs_add_error(error_name, &error,
                    grpc_attach_md_to_error(
                        GRPC_ERROR_CREATE_FROM_STATIC_STRING("Bad header"),
@@ -196,8 +201,9 @@
   }
 
   if (b->idx.named.content_type != nullptr) {
-    if (!grpc_mdelem_eq(b->idx.named.content_type->md,
-                        GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC)) {
+    if (!grpc_mdelem_static_value_eq(
+            b->idx.named.content_type->md,
+            GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC)) {
       if (grpc_slice_buf_start_eq(GRPC_MDVALUE(b->idx.named.content_type->md),
                                   EXPECTED_CONTENT_TYPE,
                                   EXPECTED_CONTENT_TYPE_LENGTH) &&
diff --git a/src/core/ext/filters/message_size/message_size_filter.cc b/src/core/ext/filters/message_size/message_size_filter.cc
index 8a422dd..a973cbc 100644
--- a/src/core/ext/filters/message_size/message_size_filter.cc
+++ b/src/core/ext/filters/message_size/message_size_filter.cc
@@ -32,75 +32,78 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/surface/call.h"
 #include "src/core/lib/surface/channel_init.h"
 
-typedef struct {
-  int max_send_size;
-  int max_recv_size;
-} message_size_limits;
-
-namespace grpc_core {
-namespace {
-
-class MessageSizeLimits : public RefCounted<MessageSizeLimits> {
- public:
-  static RefCountedPtr<MessageSizeLimits> CreateFromJson(const grpc_json* json);
-
-  const message_size_limits& limits() const { return limits_; }
-
- private:
-  // So New() can call our private ctor.
-  template <typename T, typename... Args>
-  friend T* grpc_core::New(Args&&... args);
-
-  MessageSizeLimits(int max_send_size, int max_recv_size) {
-    limits_.max_send_size = max_send_size;
-    limits_.max_recv_size = max_recv_size;
-  }
-
-  message_size_limits limits_;
-};
-
-RefCountedPtr<MessageSizeLimits> MessageSizeLimits::CreateFromJson(
-    const grpc_json* json) {
-  int max_request_message_bytes = -1;
-  int max_response_message_bytes = -1;
-  for (grpc_json* field = json->child; field != nullptr; field = field->next) {
-    if (field->key == nullptr) continue;
-    if (strcmp(field->key, "maxRequestMessageBytes") == 0) {
-      if (max_request_message_bytes >= 0) return nullptr;  // Duplicate.
-      if (field->type != GRPC_JSON_STRING && field->type != GRPC_JSON_NUMBER) {
-        return nullptr;
-      }
-      max_request_message_bytes = gpr_parse_nonnegative_int(field->value);
-      if (max_request_message_bytes == -1) return nullptr;
-    } else if (strcmp(field->key, "maxResponseMessageBytes") == 0) {
-      if (max_response_message_bytes >= 0) return nullptr;  // Duplicate.
-      if (field->type != GRPC_JSON_STRING && field->type != GRPC_JSON_NUMBER) {
-        return nullptr;
-      }
-      max_response_message_bytes = gpr_parse_nonnegative_int(field->value);
-      if (max_response_message_bytes == -1) return nullptr;
-    }
-  }
-  return MakeRefCounted<MessageSizeLimits>(max_request_message_bytes,
-                                           max_response_message_bytes);
-}
-
-}  // namespace
-}  // namespace grpc_core
-
 static void recv_message_ready(void* user_data, grpc_error* error);
 static void recv_trailing_metadata_ready(void* user_data, grpc_error* error);
 
-namespace {
+namespace grpc_core {
 
+namespace {
+size_t g_message_size_parser_index;
+}  // namespace
+
+UniquePtr<ServiceConfig::ParsedConfig> MessageSizeParser::ParsePerMethodParams(
+    const grpc_json* json, grpc_error** error) {
+  GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE);
+  int max_request_message_bytes = -1;
+  int max_response_message_bytes = -1;
+  InlinedVector<grpc_error*, 4> error_list;
+  for (grpc_json* field = json->child; field != nullptr; field = field->next) {
+    if (field->key == nullptr) continue;
+    if (strcmp(field->key, "maxRequestMessageBytes") == 0) {
+      if (max_request_message_bytes >= 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxRequestMessageBytes error:Duplicate entry"));
+      }  // Duplicate, continue parsing.
+      if (field->type != GRPC_JSON_STRING && field->type != GRPC_JSON_NUMBER) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxRequestMessageBytes error:should be of type number"));
+      } else {
+        max_request_message_bytes = gpr_parse_nonnegative_int(field->value);
+        if (max_request_message_bytes == -1) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:maxRequestMessageBytes error:should be non-negative"));
+        }
+      }
+    } else if (strcmp(field->key, "maxResponseMessageBytes") == 0) {
+      if (max_response_message_bytes >= 0) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxResponseMessageBytes error:Duplicate entry"));
+      }  // Duplicate, continue parsing
+      if (field->type != GRPC_JSON_STRING && field->type != GRPC_JSON_NUMBER) {
+        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+            "field:maxResponseMessageBytes error:should be of type number"));
+      } else {
+        max_response_message_bytes = gpr_parse_nonnegative_int(field->value);
+        if (max_response_message_bytes == -1) {
+          error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+              "field:maxResponseMessageBytes error:should be non-negative"));
+        }
+      }
+    }
+  }
+  if (!error_list.empty()) {
+    *error = GRPC_ERROR_CREATE_FROM_VECTOR("Message size parser", &error_list);
+    return nullptr;
+  }
+  return UniquePtr<ServiceConfig::ParsedConfig>(New<MessageSizeParsedObject>(
+      max_request_message_bytes, max_response_message_bytes));
+}
+
+void MessageSizeParser::Register() {
+  g_message_size_parser_index = ServiceConfig::RegisterParser(
+      UniquePtr<ServiceConfig::Parser>(New<MessageSizeParser>()));
+}
+
+size_t MessageSizeParser::ParserIndex() { return g_message_size_parser_index; }
+}  // namespace grpc_core
+
+namespace {
 struct channel_data {
-  message_size_limits limits;
-  // Maps path names to refcounted_message_size_limits structs.
-  grpc_core::RefCountedPtr<grpc_core::SliceHashTable<
-      grpc_core::RefCountedPtr<grpc_core::MessageSizeLimits>>>
-      method_limit_table;
+  grpc_core::MessageSizeParsedObject::message_size_limits limits;
+  grpc_core::RefCountedPtr<grpc_core::ServiceConfig> svc_cfg;
 };
 
 struct call_data {
@@ -116,29 +119,42 @@
     // Note: Per-method config is only available on the client, so we
     // apply the max request size to the send limit and the max response
     // size to the receive limit.
-    if (chand.method_limit_table != nullptr) {
-      grpc_core::RefCountedPtr<grpc_core::MessageSizeLimits> limits =
-          grpc_core::ServiceConfig::MethodConfigTableLookup(
-              *chand.method_limit_table, args.path);
-      if (limits != nullptr) {
-        if (limits->limits().max_send_size >= 0 &&
-            (limits->limits().max_send_size < this->limits.max_send_size ||
-             this->limits.max_send_size < 0)) {
-          this->limits.max_send_size = limits->limits().max_send_size;
-        }
-        if (limits->limits().max_recv_size >= 0 &&
-            (limits->limits().max_recv_size < this->limits.max_recv_size ||
-             this->limits.max_recv_size < 0)) {
-          this->limits.max_recv_size = limits->limits().max_recv_size;
-        }
+    const grpc_core::MessageSizeParsedObject* limits = nullptr;
+    grpc_core::ServiceConfig::CallData* svc_cfg_call_data = nullptr;
+    if (args.context != nullptr) {
+      svc_cfg_call_data = static_cast<grpc_core::ServiceConfig::CallData*>(
+          args.context[GRPC_SERVICE_CONFIG_CALL_DATA].value);
+    }
+    if (svc_cfg_call_data != nullptr) {
+      limits = static_cast<const grpc_core::MessageSizeParsedObject*>(
+          svc_cfg_call_data->GetMethodParsedObject(
+              grpc_core::MessageSizeParser::ParserIndex()));
+    } else if (chand.svc_cfg != nullptr) {
+      const auto* objs_vector =
+          chand.svc_cfg->GetMethodServiceConfigObjectsVector(args.path);
+      if (objs_vector != nullptr) {
+        limits = static_cast<const grpc_core::MessageSizeParsedObject*>(
+            (*objs_vector)[grpc_core::MessageSizeParser::ParserIndex()].get());
+      }
+    }
+    if (limits != nullptr) {
+      if (limits->limits().max_send_size >= 0 &&
+          (limits->limits().max_send_size < this->limits.max_send_size ||
+           this->limits.max_send_size < 0)) {
+        this->limits.max_send_size = limits->limits().max_send_size;
+      }
+      if (limits->limits().max_recv_size >= 0 &&
+          (limits->limits().max_recv_size < this->limits.max_recv_size ||
+           this->limits.max_recv_size < 0)) {
+        this->limits.max_recv_size = limits->limits().max_recv_size;
       }
     }
   }
 
   ~call_data() { GRPC_ERROR_UNREF(error); }
 
-  grpc_call_combiner* call_combiner;
-  message_size_limits limits;
+  grpc_core::CallCombiner* call_combiner;
+  grpc_core::MessageSizeParsedObject::message_size_limits limits;
   // Receive closures are chained: we inject this closure as the
   // recv_message_ready up-call on transport_stream_op, and remember to
   // call our next_recv_message_ready member after handling it.
@@ -284,9 +300,9 @@
   return without_minimal_stack;
 }
 
-message_size_limits get_message_size_limits(
+grpc_core::MessageSizeParsedObject::message_size_limits get_message_size_limits(
     const grpc_channel_args* channel_args) {
-  message_size_limits lim;
+  grpc_core::MessageSizeParsedObject::message_size_limits lim;
   lim.max_send_size =
       default_size(channel_args, GRPC_DEFAULT_MAX_SEND_MESSAGE_LENGTH);
   lim.max_recv_size =
@@ -313,18 +329,27 @@
                                      grpc_channel_element_args* args) {
   GPR_ASSERT(!args->is_last);
   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
+  new (chand) channel_data();
   chand->limits = get_message_size_limits(args->channel_args);
-  // Get method config table from channel args.
+  // TODO(yashykt): We only need to read GRPC_ARG_SERVICE_CONFIG in the case of
+  // direct channels. (Service config is otherwise stored in the call_context by
+  // client_channel filter.) If we ever need a second filter that also needs to
+  // parse GRPC_ARG_SERVICE_CONFIG, we should refactor this code and add a
+  // separate filter that reads GRPC_ARG_SERVICE_CONFIG and saves the parsed
+  // config in the call_context.
   const grpc_arg* channel_arg =
       grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVICE_CONFIG);
   const char* service_config_str = grpc_channel_arg_get_string(channel_arg);
   if (service_config_str != nullptr) {
-    grpc_core::RefCountedPtr<grpc_core::ServiceConfig> service_config =
-        grpc_core::ServiceConfig::Create(service_config_str);
-    if (service_config != nullptr) {
-      chand->method_limit_table = service_config->CreateMethodConfigTable(
-          grpc_core::MessageSizeLimits::CreateFromJson);
+    grpc_error* service_config_error = GRPC_ERROR_NONE;
+    auto svc_cfg = grpc_core::ServiceConfig::Create(service_config_str,
+                                                    &service_config_error);
+    if (service_config_error == GRPC_ERROR_NONE) {
+      chand->svc_cfg = std::move(svc_cfg);
+    } else {
+      gpr_log(GPR_ERROR, "%s", grpc_error_string(service_config_error));
     }
+    GRPC_ERROR_UNREF(service_config_error);
   }
   return GRPC_ERROR_NONE;
 }
@@ -332,7 +357,7 @@
 // Destructor for channel_data.
 static void destroy_channel_elem(grpc_channel_element* elem) {
   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
-  chand->method_limit_table.reset();
+  chand->~channel_data();
 }
 
 const grpc_channel_filter grpc_message_size_filter = {
@@ -348,18 +373,34 @@
     grpc_channel_next_get_info,
     "message_size"};
 
+// Used for GRPC_CLIENT_SUBCHANNEL
+static bool maybe_add_message_size_filter_subchannel(
+    grpc_channel_stack_builder* builder, void* arg) {
+  const grpc_channel_args* channel_args =
+      grpc_channel_stack_builder_get_channel_arguments(builder);
+  if (grpc_channel_args_want_minimal_stack(channel_args)) {
+    return true;
+  }
+  return grpc_channel_stack_builder_prepend_filter(
+      builder, &grpc_message_size_filter, nullptr, nullptr);
+}
+
+// Used for GRPC_CLIENT_DIRECT_CHANNEL and GRPC_SERVER_CHANNEL. Adds the filter
+// only if message size limits or service config is specified.
 static bool maybe_add_message_size_filter(grpc_channel_stack_builder* builder,
                                           void* arg) {
   const grpc_channel_args* channel_args =
       grpc_channel_stack_builder_get_channel_arguments(builder);
   bool enable = false;
-  message_size_limits lim = get_message_size_limits(channel_args);
+  grpc_core::MessageSizeParsedObject::message_size_limits lim =
+      get_message_size_limits(channel_args);
   if (lim.max_send_size != -1 || lim.max_recv_size != -1) {
     enable = true;
   }
   const grpc_arg* a =
       grpc_channel_args_find(channel_args, GRPC_ARG_SERVICE_CONFIG);
-  if (a != nullptr) {
+  const char* svc_cfg_str = grpc_channel_arg_get_string(a);
+  if (svc_cfg_str != nullptr) {
     enable = true;
   }
   if (enable) {
@@ -371,15 +412,16 @@
 }
 
 void grpc_message_size_filter_init(void) {
-  grpc_channel_init_register_stage(GRPC_CLIENT_SUBCHANNEL,
-                                   GRPC_CHANNEL_INIT_BUILTIN_PRIORITY,
-                                   maybe_add_message_size_filter, nullptr);
+  grpc_channel_init_register_stage(
+      GRPC_CLIENT_SUBCHANNEL, GRPC_CHANNEL_INIT_BUILTIN_PRIORITY,
+      maybe_add_message_size_filter_subchannel, nullptr);
   grpc_channel_init_register_stage(GRPC_CLIENT_DIRECT_CHANNEL,
                                    GRPC_CHANNEL_INIT_BUILTIN_PRIORITY,
                                    maybe_add_message_size_filter, nullptr);
   grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL,
                                    GRPC_CHANNEL_INIT_BUILTIN_PRIORITY,
                                    maybe_add_message_size_filter, nullptr);
+  grpc_core::MessageSizeParser::Register();
 }
 
 void grpc_message_size_filter_shutdown(void) {}
diff --git a/src/core/ext/filters/message_size/message_size_filter.h b/src/core/ext/filters/message_size/message_size_filter.h
index f66636e..cab8bd9 100644
--- a/src/core/ext/filters/message_size/message_size_filter.h
+++ b/src/core/ext/filters/message_size/message_size_filter.h
@@ -19,8 +19,41 @@
 
 #include <grpc/support/port_platform.h>
 
+#include "src/core/ext/filters/client_channel/service_config.h"
 #include "src/core/lib/channel/channel_stack.h"
 
 extern const grpc_channel_filter grpc_message_size_filter;
 
+namespace grpc_core {
+
+class MessageSizeParsedObject : public ServiceConfig::ParsedConfig {
+ public:
+  struct message_size_limits {
+    int max_send_size;
+    int max_recv_size;
+  };
+
+  MessageSizeParsedObject(int max_send_size, int max_recv_size) {
+    limits_.max_send_size = max_send_size;
+    limits_.max_recv_size = max_recv_size;
+  }
+
+  const message_size_limits& limits() const { return limits_; }
+
+ private:
+  message_size_limits limits_;
+};
+
+class MessageSizeParser : public ServiceConfig::Parser {
+ public:
+  UniquePtr<ServiceConfig::ParsedConfig> ParsePerMethodParams(
+      const grpc_json* json, grpc_error** error) override;
+
+  static void Register();
+
+  static size_t ParserIndex();
+};
+
+}  // namespace grpc_core
+
 #endif /* GRPC_CORE_EXT_FILTERS_MESSAGE_SIZE_MESSAGE_SIZE_FILTER_H */
diff --git a/src/core/ext/transport/chttp2/alpn/alpn.h b/src/core/ext/transport/chttp2/alpn/alpn.h
index 0042eaf..e2ffe4e4 100644
--- a/src/core/ext/transport/chttp2/alpn/alpn.h
+++ b/src/core/ext/transport/chttp2/alpn/alpn.h
@@ -23,7 +23,7 @@
 
 #include <string.h>
 
-/* Retuns 1 if the version is supported, 0 otherwise. */
+/* Returns 1 if the version is supported, 0 otherwise. */
 int grpc_chttp2_is_alpn_version_supported(const char* version, size_t size);
 
 /* Returns the number of protocol versions to advertise */
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_plugin.cc b/src/core/ext/transport/chttp2/transport/chttp2_plugin.cc
index 531ea73..ac13d73 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_plugin.cc
+++ b/src/core/ext/transport/chttp2/transport/chttp2_plugin.cc
@@ -20,16 +20,18 @@
 
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/debug/trace.h"
-#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/transport/metadata.h"
 
+GPR_GLOBAL_CONFIG_DEFINE_BOOL(
+    grpc_experimental_disable_flow_control, false,
+    "If set, flow control will be effectively disabled. Max out all values and "
+    "assume the remote peer does the same. Thus we can ignore any flow control "
+    "bookkeeping, error checking, and decision making");
+
 void grpc_chttp2_plugin_init(void) {
-  g_flow_control_enabled = true;
-  char* env_variable = gpr_getenv("GRPC_EXPERIMENTAL_DISABLE_FLOW_CONTROL");
-  if (env_variable != nullptr) {
-    g_flow_control_enabled = false;
-    gpr_free(env_variable);
-  }
+  g_flow_control_enabled =
+      !GPR_GLOBAL_CONFIG_GET(grpc_experimental_disable_flow_control);
 }
 
 void grpc_chttp2_plugin_shutdown(void) {}
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
index 404539c..ccc3077 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
@@ -119,7 +119,7 @@
 
 static void connectivity_state_set(grpc_chttp2_transport* t,
                                    grpc_connectivity_state state,
-                                   grpc_error* error, const char* reason);
+                                   const char* reason);
 
 static void benign_reclaimer_locked(void* t, grpc_error* error);
 static void destructive_reclaimer_locked(void* t, grpc_error* error);
@@ -592,8 +592,7 @@
     }
     GPR_ASSERT(error != GRPC_ERROR_NONE);
     t->closed_with_error = GRPC_ERROR_REF(error);
-    connectivity_state_set(t, GRPC_CHANNEL_SHUTDOWN, GRPC_ERROR_REF(error),
-                           "close_transport");
+    connectivity_state_set(t, GRPC_CHANNEL_SHUTDOWN, "close_transport");
     if (t->ping_state.is_delayed_ping_timer_set) {
       grpc_timer_cancel(&t->ping_state.delayed_ping_timer);
     }
@@ -645,17 +644,23 @@
 }
 #endif
 
-grpc_chttp2_stream::grpc_chttp2_stream(grpc_chttp2_transport* t,
-                                       grpc_stream_refcount* refcount,
-                                       const void* server_data,
-                                       gpr_arena* arena)
-    : t(t), refcount(refcount), metadata_buffer{{arena}, {arena}} {
+grpc_chttp2_stream::Reffer::Reffer(grpc_chttp2_stream* s) {
   /* We reserve one 'active stream' that's dropped when the stream is
      read-closed. The others are for Chttp2IncomingByteStreams that are
      actively reading */
-  GRPC_CHTTP2_STREAM_REF(this, "chttp2");
-  GRPC_CHTTP2_REF_TRANSPORT(t, "stream");
+  GRPC_CHTTP2_STREAM_REF(s, "chttp2");
+  GRPC_CHTTP2_REF_TRANSPORT(s->t, "stream");
+}
 
+grpc_chttp2_stream::grpc_chttp2_stream(grpc_chttp2_transport* t,
+                                       grpc_stream_refcount* refcount,
+                                       const void* server_data,
+                                       grpc_core::Arena* arena)
+    : t(t),
+      refcount(refcount),
+      reffer(this),
+      metadata_buffer{grpc_chttp2_incoming_metadata_buffer(arena),
+                      grpc_chttp2_incoming_metadata_buffer(arena)} {
   if (server_data) {
     id = static_cast<uint32_t>((uintptr_t)server_data);
     *t->accepting_stream = this;
@@ -674,8 +679,6 @@
   grpc_slice_buffer_init(&frame_storage);
   grpc_slice_buffer_init(&unprocessed_incoming_frames_buffer);
   grpc_slice_buffer_init(&flow_controlled_buffer);
-  grpc_slice_buffer_init(&compressed_data_buffer);
-  grpc_slice_buffer_init(&decompressed_data_buffer);
 
   GRPC_CLOSURE_INIT(&complete_fetch_locked, ::complete_fetch_locked, this,
                     grpc_combiner_scheduler(t->combiner));
@@ -699,8 +702,13 @@
 
   grpc_slice_buffer_destroy_internal(&unprocessed_incoming_frames_buffer);
   grpc_slice_buffer_destroy_internal(&frame_storage);
-  grpc_slice_buffer_destroy_internal(&compressed_data_buffer);
-  grpc_slice_buffer_destroy_internal(&decompressed_data_buffer);
+  if (stream_compression_method != GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS) {
+    grpc_slice_buffer_destroy_internal(&compressed_data_buffer);
+  }
+  if (stream_decompression_method !=
+      GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) {
+    grpc_slice_buffer_destroy_internal(&decompressed_data_buffer);
+  }
 
   grpc_chttp2_list_remove_stalled_by_transport(t, this);
   grpc_chttp2_list_remove_stalled_by_stream(t, this);
@@ -736,7 +744,7 @@
 
 static int init_stream(grpc_transport* gt, grpc_stream* gs,
                        grpc_stream_refcount* refcount, const void* server_data,
-                       gpr_arena* arena) {
+                       grpc_core::Arena* arena) {
   GPR_TIMER_SCOPE("init_stream", 0);
   grpc_chttp2_transport* t = reinterpret_cast<grpc_chttp2_transport*>(gt);
   new (gs) grpc_chttp2_stream(t, refcount, server_data, arena);
@@ -754,12 +762,15 @@
   GPR_TIMER_SCOPE("destroy_stream", 0);
   grpc_chttp2_transport* t = reinterpret_cast<grpc_chttp2_transport*>(gt);
   grpc_chttp2_stream* s = reinterpret_cast<grpc_chttp2_stream*>(gs);
-
-  if (s->stream_compression_ctx != nullptr) {
+  if (s->stream_compression_method !=
+          GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS &&
+      s->stream_compression_ctx != nullptr) {
     grpc_stream_compression_context_destroy(s->stream_compression_ctx);
     s->stream_compression_ctx = nullptr;
   }
-  if (s->stream_decompression_ctx != nullptr) {
+  if (s->stream_decompression_method !=
+          GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS &&
+      s->stream_decompression_ctx != nullptr) {
     grpc_stream_compression_context_destroy(s->stream_decompression_ctx);
     s->stream_decompression_ctx = nullptr;
   }
@@ -1166,8 +1177,7 @@
 
   /* lie: use transient failure from the transport to indicate goaway has been
    * received */
-  connectivity_state_set(t, GRPC_CHANNEL_TRANSIENT_FAILURE,
-                         GRPC_ERROR_REF(t->goaway_error), "got_goaway");
+  connectivity_state_set(t, GRPC_CHANNEL_TRANSIENT_FAILURE, "got_goaway");
 }
 
 static void maybe_start_some_streams(grpc_chttp2_transport* t) {
@@ -1189,10 +1199,8 @@
     t->next_stream_id += 2;
 
     if (t->next_stream_id >= MAX_CLIENT_STREAM_ID) {
-      connectivity_state_set(
-          t, GRPC_CHANNEL_TRANSIENT_FAILURE,
-          GRPC_ERROR_CREATE_FROM_STATIC_STRING("Stream IDs exhausted"),
-          "no_more_stream_ids");
+      connectivity_state_set(t, GRPC_CHANNEL_TRANSIENT_FAILURE,
+                             "no_more_stream_ids");
     }
 
     grpc_chttp2_stream_map_add(&t->stream_map, s->id, s);
@@ -1241,7 +1249,7 @@
     return;
   }
   closure->next_data.scratch -= CLOSURE_BARRIER_FIRST_REF_BIT;
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     const char* errstr = grpc_error_string(error);
     gpr_log(
         GPR_INFO,
@@ -1280,8 +1288,8 @@
 
 static bool contains_non_ok_status(grpc_metadata_batch* batch) {
   if (batch->idx.named.grpc_status != nullptr) {
-    return !grpc_mdelem_eq(batch->idx.named.grpc_status->md,
-                           GRPC_MDELEM_GRPC_STATUS_0);
+    return !grpc_mdelem_static_value_eq(batch->idx.named.grpc_status->md,
+                                        GRPC_MDELEM_GRPC_STATUS_0);
   }
   return false;
 }
@@ -1393,7 +1401,7 @@
 
   s->context = op->payload->context;
   s->traced = op->is_traced;
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     char* str = grpc_transport_stream_op_batch_string(op);
     gpr_log(GPR_INFO, "perform_stream_op_locked: %s; on_complete = %p", str,
             op->on_complete);
@@ -1412,7 +1420,7 @@
   // on_complete will be null if and only if there are no send ops in the batch.
   if (on_complete != nullptr) {
     // This batch has send ops. Use final_data as a barrier until enqueue time;
-    // the inital counter is dropped at the end of this function.
+    // the initial counter is dropped at the end of this function.
     on_complete->next_data.scratch = CLOSURE_BARRIER_FIRST_REF_BIT;
     on_complete->error_data.error = GRPC_ERROR_NONE;
   }
@@ -1440,7 +1448,12 @@
             true, &s->stream_compression_method) == 0) {
       s->stream_compression_method = GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS;
     }
-
+    if (s->stream_compression_method !=
+        GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS) {
+      s->uncompressed_data_size = 0;
+      s->stream_compression_ctx = nullptr;
+      grpc_slice_buffer_init(&s->compressed_data_buffer);
+    }
     s->send_initial_metadata_finished = add_closure_barrier(on_complete);
     s->send_initial_metadata =
         op_payload->send_initial_metadata.send_initial_metadata;
@@ -1692,7 +1705,7 @@
     }
   }
 
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     char* str = grpc_transport_stream_op_batch_string(op);
     gpr_log(GPR_INFO, "perform_stream_op[s=%p]: %s", s, str);
     gpr_free(str);
@@ -1859,7 +1872,7 @@
 
 static void perform_transport_op(grpc_transport* gt, grpc_transport_op* op) {
   grpc_chttp2_transport* t = reinterpret_cast<grpc_chttp2_transport*>(gt);
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     char* msg = grpc_transport_op_string(op);
     gpr_log(GPR_INFO, "perform_transport_op[t=%p]: %s", t, msg);
     gpr_free(msg);
@@ -1996,27 +2009,39 @@
         !s->seen_error && s->recv_trailing_metadata_finished != nullptr) {
       /* Maybe some SYNC_FLUSH data is left in frame_storage. Consume them and
        * maybe decompress the next 5 bytes in the stream. */
-      bool end_of_context;
-      if (!s->stream_decompression_ctx) {
-        s->stream_decompression_ctx = grpc_stream_compression_context_create(
-            s->stream_decompression_method);
-      }
-      if (!grpc_stream_decompress(
-              s->stream_decompression_ctx, &s->frame_storage,
-              &s->unprocessed_incoming_frames_buffer, nullptr,
-              GRPC_HEADER_SIZE_IN_BYTES, &end_of_context)) {
-        grpc_slice_buffer_reset_and_unref_internal(&s->frame_storage);
-        grpc_slice_buffer_reset_and_unref_internal(
-            &s->unprocessed_incoming_frames_buffer);
-        s->seen_error = true;
-      } else {
+      if (s->stream_decompression_method ==
+          GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) {
+        grpc_slice_buffer_move_first(&s->frame_storage,
+                                     GRPC_HEADER_SIZE_IN_BYTES,
+                                     &s->unprocessed_incoming_frames_buffer);
         if (s->unprocessed_incoming_frames_buffer.length > 0) {
           s->unprocessed_incoming_frames_decompressed = true;
           pending_data = true;
         }
-        if (end_of_context) {
-          grpc_stream_compression_context_destroy(s->stream_decompression_ctx);
-          s->stream_decompression_ctx = nullptr;
+      } else {
+        bool end_of_context;
+        if (!s->stream_decompression_ctx) {
+          s->stream_decompression_ctx = grpc_stream_compression_context_create(
+              s->stream_decompression_method);
+        }
+        if (!grpc_stream_decompress(
+                s->stream_decompression_ctx, &s->frame_storage,
+                &s->unprocessed_incoming_frames_buffer, nullptr,
+                GRPC_HEADER_SIZE_IN_BYTES, &end_of_context)) {
+          grpc_slice_buffer_reset_and_unref_internal(&s->frame_storage);
+          grpc_slice_buffer_reset_and_unref_internal(
+              &s->unprocessed_incoming_frames_buffer);
+          s->seen_error = true;
+        } else {
+          if (s->unprocessed_incoming_frames_buffer.length > 0) {
+            s->unprocessed_incoming_frames_decompressed = true;
+            pending_data = true;
+          }
+          if (end_of_context) {
+            grpc_stream_compression_context_destroy(
+                s->stream_decompression_ctx);
+            s->stream_decompression_ctx = nullptr;
+          }
         }
       }
     }
@@ -2594,10 +2619,13 @@
 
 static void start_bdp_ping_locked(void* tp, grpc_error* error) {
   grpc_chttp2_transport* t = static_cast<grpc_chttp2_transport*>(tp);
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     gpr_log(GPR_INFO, "%s: Start BDP ping err=%s", t->peer_string,
             grpc_error_string(error));
   }
+  if (error != GRPC_ERROR_NONE || t->closed_with_error != GRPC_ERROR_NONE) {
+    return;
+  }
   /* Reset the keepalive ping timer */
   if (t->keepalive_state == GRPC_CHTTP2_KEEPALIVE_STATE_WAITING) {
     grpc_timer_cancel(&t->keepalive_ping_timer);
@@ -2607,11 +2635,11 @@
 
 static void finish_bdp_ping_locked(void* tp, grpc_error* error) {
   grpc_chttp2_transport* t = static_cast<grpc_chttp2_transport*>(tp);
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     gpr_log(GPR_INFO, "%s: Complete BDP ping err=%s", t->peer_string,
             grpc_error_string(error));
   }
-  if (error != GRPC_ERROR_NONE) {
+  if (error != GRPC_ERROR_NONE || t->closed_with_error != GRPC_ERROR_NONE) {
     GRPC_CHTTP2_UNREF_TRANSPORT(t, "bdp_ping");
     return;
   }
@@ -2739,7 +2767,7 @@
   if (t->channelz_socket != nullptr) {
     t->channelz_socket->RecordKeepaliveSent();
   }
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     gpr_log(GPR_INFO, "%s: Start keepalive ping", t->peer_string);
   }
   GRPC_CHTTP2_REF_TRANSPORT(t, "keepalive watchdog");
@@ -2752,7 +2780,7 @@
   grpc_chttp2_transport* t = static_cast<grpc_chttp2_transport*>(arg);
   if (t->keepalive_state == GRPC_CHTTP2_KEEPALIVE_STATE_PINGING) {
     if (error == GRPC_ERROR_NONE) {
-      if (grpc_http_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
         gpr_log(GPR_INFO, "%s: Finish keepalive ping", t->peer_string);
       }
       t->keepalive_state = GRPC_CHTTP2_KEEPALIVE_STATE_WAITING;
@@ -2796,9 +2824,9 @@
 
 static void connectivity_state_set(grpc_chttp2_transport* t,
                                    grpc_connectivity_state state,
-                                   grpc_error* error, const char* reason) {
+                                   const char* reason) {
   GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "set connectivity_state=%d", state));
-  grpc_connectivity_state_set(&t->channel_callback.state_tracker, state, error,
+  grpc_connectivity_state_set(&t->channel_callback.state_tracker, state,
                               reason);
 }
 
@@ -2936,6 +2964,8 @@
 }
 
 void Chttp2IncomingByteStream::MaybeCreateStreamDecompressionCtx() {
+  GPR_DEBUG_ASSERT(stream_->stream_decompression_method !=
+                   GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS);
   if (!stream_->stream_decompression_ctx) {
     stream_->stream_decompression_ctx = grpc_stream_compression_context_create(
         stream_->stream_decompression_method);
@@ -2946,7 +2976,9 @@
   GPR_TIMER_SCOPE("incoming_byte_stream_pull", 0);
   grpc_error* error;
   if (stream_->unprocessed_incoming_frames_buffer.length > 0) {
-    if (!stream_->unprocessed_incoming_frames_decompressed) {
+    if (!stream_->unprocessed_incoming_frames_decompressed &&
+        stream_->stream_decompression_method !=
+            GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) {
       bool end_of_context;
       MaybeCreateStreamDecompressionCtx();
       if (!grpc_stream_decompress(stream_->stream_decompression_ctx,
@@ -3058,7 +3090,7 @@
       grpc_chttp2_stream_map_size(&t->stream_map) == 0) {
     /* Channel with no active streams: send a goaway to try and make it
      * disconnect cleanly */
-    if (grpc_resource_quota_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
       gpr_log(GPR_INFO, "HTTP2: %s - send goaway to free memory",
               t->peer_string);
     }
@@ -3066,7 +3098,8 @@
                 grpc_error_set_int(
                     GRPC_ERROR_CREATE_FROM_STATIC_STRING("Buffers full"),
                     GRPC_ERROR_INT_HTTP2_ERROR, GRPC_HTTP2_ENHANCE_YOUR_CALM));
-  } else if (error == GRPC_ERROR_NONE && grpc_resource_quota_trace.enabled()) {
+  } else if (error == GRPC_ERROR_NONE &&
+             GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
     gpr_log(GPR_INFO,
             "HTTP2: %s - skip benign reclamation, there are still %" PRIdPTR
             " streams",
@@ -3087,7 +3120,7 @@
   if (error == GRPC_ERROR_NONE && n > 0) {
     grpc_chttp2_stream* s = static_cast<grpc_chttp2_stream*>(
         grpc_chttp2_stream_map_rand(&t->stream_map));
-    if (grpc_resource_quota_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
       gpr_log(GPR_INFO, "HTTP2: %s - abandon stream id %d", t->peer_string,
               s->id);
     }
diff --git a/src/core/ext/transport/chttp2/transport/flow_control.h b/src/core/ext/transport/chttp2/transport/flow_control.h
index 120fefc..49e206f 100644
--- a/src/core/ext/transport/chttp2/transport/flow_control.h
+++ b/src/core/ext/transport/chttp2/transport/flow_control.h
@@ -127,7 +127,7 @@
             StreamFlowControl* sfc);
   void Finish();
 
-  const bool enabled_ = grpc_flowctl_trace.enabled();
+  const bool enabled_ = GRPC_TRACE_FLAG_ENABLED(grpc_flowctl_trace);
 
   TransportFlowControl* tfc_;
   StreamFlowControl* sfc_;
diff --git a/src/core/ext/transport/chttp2/transport/frame_settings.cc b/src/core/ext/transport/chttp2/transport/frame_settings.cc
index ed1554e..3f84679 100644
--- a/src/core/ext/transport/chttp2/transport/frame_settings.cc
+++ b/src/core/ext/transport/chttp2/transport/frame_settings.cc
@@ -217,19 +217,20 @@
               parser->incoming_settings[id] != parser->value) {
             t->initial_window_update += static_cast<int64_t>(parser->value) -
                                         parser->incoming_settings[id];
-            if (grpc_http_trace.enabled() || grpc_flowctl_trace.enabled()) {
+            if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace) ||
+                GRPC_TRACE_FLAG_ENABLED(grpc_flowctl_trace)) {
               gpr_log(GPR_INFO, "%p[%s] adding %d for initial_window change", t,
                       t->is_client ? "cli" : "svr",
                       static_cast<int>(t->initial_window_update));
             }
           }
           parser->incoming_settings[id] = parser->value;
-          if (grpc_http_trace.enabled()) {
+          if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
             gpr_log(GPR_INFO, "CHTTP2:%s:%s: got setting %s = %d",
                     t->is_client ? "CLI" : "SVR", t->peer_string, sp->name,
                     parser->value);
           }
-        } else if (grpc_http_trace.enabled()) {
+        } else if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
           gpr_log(GPR_ERROR, "CHTTP2: Ignoring unknown setting %d (value %d)",
                   parser->id, parser->value);
         }
diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder.cc b/src/core/ext/transport/chttp2/transport/hpack_encoder.cc
index 9b4c3ce..d2607e9 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_encoder.cc
+++ b/src/core/ext/transport/chttp2/transport/hpack_encoder.cc
@@ -56,7 +56,7 @@
 /* don't consider adding anything bigger than this to the hpack table */
 #define MAX_DECODER_SPACE_USAGE 512
 
-static grpc_slice_refcount terminal_slice_refcount = {nullptr, nullptr};
+static grpc_slice_refcount terminal_slice_refcount;
 static const grpc_slice terminal_slice = {
     &terminal_slice_refcount, /* refcount */
     {{0, nullptr}}            /* data.refcounted */
@@ -461,7 +461,7 @@
         "Reserved header (colon-prefixed) happening after regular ones.");
   }
 
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     char* k = grpc_slice_to_c_string(GRPC_MDKEY(elem));
     char* v = nullptr;
     if (grpc_is_binary_header(GRPC_MDKEY(elem))) {
@@ -660,7 +660,7 @@
     }
   }
   c->advertise_table_size_change = 1;
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     gpr_log(GPR_INFO, "set max table size from encoder to %d", max_table_size);
   }
 }
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.cc b/src/core/ext/transport/chttp2/transport/hpack_parser.cc
index 5bcdb4e..6e42212 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.cc
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.cc
@@ -624,7 +624,7 @@
 /* emission helpers */
 static grpc_error* on_hdr(grpc_chttp2_hpack_parser* p, grpc_mdelem md,
                           int add_to_table) {
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     char* k = grpc_slice_to_c_string(GRPC_MDKEY(md));
     char* v = nullptr;
     if (grpc_is_binary_header(GRPC_MDKEY(md))) {
@@ -994,7 +994,7 @@
 /* finish parsing a max table size change */
 static grpc_error* finish_max_tbl_size(grpc_chttp2_hpack_parser* p,
                                        const uint8_t* cur, const uint8_t* end) {
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     gpr_log(GPR_INFO, "MAX TABLE SIZE: %d", p->index);
   }
   grpc_error* err =
@@ -1616,6 +1616,12 @@
     s->stream_decompression_method =
         GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS;
   }
+
+  if (s->stream_decompression_method !=
+      GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) {
+    s->stream_decompression_ctx = nullptr;
+    grpc_slice_buffer_init(&s->decompressed_data_buffer);
+  }
 }
 
 grpc_error* grpc_chttp2_header_parser_parse(void* hpack_parser,
diff --git a/src/core/ext/transport/chttp2/transport/hpack_table.cc b/src/core/ext/transport/chttp2/transport/hpack_table.cc
index fcfb018..16aeb49 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_table.cc
+++ b/src/core/ext/transport/chttp2/transport/hpack_table.cc
@@ -247,7 +247,7 @@
   if (tbl->max_bytes == max_bytes) {
     return;
   }
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     gpr_log(GPR_INFO, "Update hpack parser max size to %d", max_bytes);
   }
   while (tbl->mem_used > max_bytes) {
@@ -270,7 +270,7 @@
     gpr_free(msg);
     return err;
   }
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     gpr_log(GPR_INFO, "Update hpack parser table size to %d", bytes);
   }
   while (tbl->mem_used > bytes) {
diff --git a/src/core/ext/transport/chttp2/transport/incoming_metadata.cc b/src/core/ext/transport/chttp2/transport/incoming_metadata.cc
index 1e04be7..02623c9 100644
--- a/src/core/ext/transport/chttp2/transport/incoming_metadata.cc
+++ b/src/core/ext/transport/chttp2/transport/incoming_metadata.cc
@@ -36,7 +36,7 @@
     buffer->count++;
   } else {
     storage = static_cast<grpc_linked_mdelem*>(
-        gpr_arena_alloc(buffer->arena, sizeof(grpc_linked_mdelem)));
+        buffer->arena->Alloc(sizeof(grpc_linked_mdelem)));
   }
   return grpc_metadata_batch_add_tail(&buffer->batch, storage, elem);
 }
diff --git a/src/core/ext/transport/chttp2/transport/incoming_metadata.h b/src/core/ext/transport/chttp2/transport/incoming_metadata.h
index 4a9a592..b63caa1 100644
--- a/src/core/ext/transport/chttp2/transport/incoming_metadata.h
+++ b/src/core/ext/transport/chttp2/transport/incoming_metadata.h
@@ -24,7 +24,8 @@
 #include "src/core/lib/transport/transport.h"
 
 struct grpc_chttp2_incoming_metadata_buffer {
-  grpc_chttp2_incoming_metadata_buffer(gpr_arena* arena) : arena(arena) {
+  explicit grpc_chttp2_incoming_metadata_buffer(grpc_core::Arena* arena)
+      : arena(arena) {
     grpc_metadata_batch_init(&batch);
     batch.deadline = GRPC_MILLIS_INF_FUTURE;
   }
@@ -34,7 +35,7 @@
 
   static constexpr size_t kPreallocatedMDElem = 10;
 
-  gpr_arena* arena;
+  grpc_core::Arena* arena;
   size_t size = 0;   // total size of metadata.
   size_t count = 0;  // minimum of count of metadata and kPreallocatedMDElem.
   // These preallocated mdelems are used while count < kPreallocatedMDElem.
diff --git a/src/core/ext/transport/chttp2/transport/internal.h b/src/core/ext/transport/chttp2/transport/internal.h
index 760324c..bfe7745 100644
--- a/src/core/ext/transport/chttp2/transport/internal.h
+++ b/src/core/ext/transport/chttp2/transport/internal.h
@@ -238,7 +238,7 @@
   // switch to std::shared_ptr<>.
   void Ref() { refs_.Ref(); }
   void Unref() {
-    if (refs_.Unref()) {
+    if (GPR_UNLIKELY(refs_.Unref())) {
       grpc_core::Delete(this);
     }
   }
@@ -504,12 +504,18 @@
 
 struct grpc_chttp2_stream {
   grpc_chttp2_stream(grpc_chttp2_transport* t, grpc_stream_refcount* refcount,
-                     const void* server_data, gpr_arena* arena);
+                     const void* server_data, grpc_core::Arena* arena);
   ~grpc_chttp2_stream();
 
   void* context;
   grpc_chttp2_transport* t;
   grpc_stream_refcount* refcount;
+  // Reffer is a 0-len structure, simply reffing `t` and `refcount` in its ctor
+  // before initializing the rest of the stream, to avoid cache misses. This
+  // field MUST be right after `t` and `refcount`.
+  struct Reffer {
+    explicit Reffer(grpc_chttp2_stream* s);
+  } reffer;
 
   grpc_closure destroy_stream;
   grpc_closure* destroy_stream_arg;
@@ -577,10 +583,6 @@
 
   grpc_slice_buffer frame_storage; /* protected by t combiner */
 
-  /* Accessed only by transport thread when stream->pending_byte_stream == false
-   * Accessed only by application thread when stream->pending_byte_stream ==
-   * true */
-  grpc_slice_buffer unprocessed_incoming_frames_buffer;
   grpc_closure* on_next = nullptr;  /* protected by t combiner */
   bool pending_byte_stream = false; /* protected by t combiner */
   // cached length of buffer to be used by the transport thread in cases where
@@ -588,6 +590,10 @@
   // application threads are allowed to modify
   // unprocessed_incoming_frames_buffer
   size_t unprocessed_incoming_frames_buffer_cached_length = 0;
+  /* Accessed only by transport thread when stream->pending_byte_stream == false
+   * Accessed only by application thread when stream->pending_byte_stream ==
+   * true */
+  grpc_slice_buffer unprocessed_incoming_frames_buffer;
   grpc_closure reset_byte_stream;
   grpc_error* byte_stream_error = GRPC_ERROR_NONE; /* protected by t combiner */
   bool received_last_frame = false;                /* protected by t combiner */
@@ -627,19 +633,8 @@
       GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS;
   /* Stream decompression method to be used. */
   grpc_stream_compression_method stream_decompression_method =
-      GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS;
-  /** Stream compression decompress context */
-  grpc_stream_compression_context* stream_decompression_ctx = nullptr;
-  /** Stream compression compress context */
-  grpc_stream_compression_context* stream_compression_ctx = nullptr;
+      GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS;
 
-  /** Buffer storing data that is compressed but not sent */
-  grpc_slice_buffer compressed_data_buffer;
-  /** Amount of uncompressed bytes sent out when compressed_data_buffer is
-   * emptied */
-  size_t uncompressed_data_size = 0;
-  /** Temporary buffer storing decompressed data */
-  grpc_slice_buffer decompressed_data_buffer;
   /** Whether bytes stored in unprocessed_incoming_byte_stream is decompressed
    */
   bool unprocessed_incoming_frames_decompressed = false;
@@ -649,6 +644,22 @@
   size_t decompressed_header_bytes = 0;
   /** Byte counter for number of bytes written */
   size_t byte_counter = 0;
+
+  /** Amount of uncompressed bytes sent out when compressed_data_buffer is
+   * emptied */
+  size_t uncompressed_data_size;
+  /** Stream compression compress context */
+  grpc_stream_compression_context* stream_compression_ctx;
+  /** Buffer storing data that is compressed but not sent */
+  grpc_slice_buffer compressed_data_buffer;
+
+  /** Stream compression decompress context */
+  grpc_stream_compression_context* stream_decompression_ctx;
+  /** Temporary buffer storing decompressed data.
+   * Initialized, used, and destroyed only when stream uses (non-identity)
+   * compression.
+   */
+  grpc_slice_buffer decompressed_data_buffer;
 };
 
 /** Transport writing call flow:
@@ -760,11 +771,12 @@
 // extern grpc_core::TraceFlag grpc_http_trace;
 // extern grpc_core::TraceFlag grpc_flowctl_trace;
 
-#define GRPC_CHTTP2_IF_TRACING(stmt) \
-  if (!(grpc_http_trace.enabled()))  \
-    ;                                \
-  else                               \
-    stmt
+#define GRPC_CHTTP2_IF_TRACING(stmt)                \
+  do {                                              \
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) { \
+      (stmt);                                       \
+    }                                               \
+  } while (0)
 
 void grpc_chttp2_fake_status(grpc_chttp2_transport* t,
                              grpc_chttp2_stream* stream, grpc_error* error);
diff --git a/src/core/ext/transport/chttp2/transport/parsing.cc b/src/core/ext/transport/chttp2/transport/parsing.cc
index 84b2275..15648c0 100644
--- a/src/core/ext/transport/chttp2/transport/parsing.cc
+++ b/src/core/ext/transport/chttp2/transport/parsing.cc
@@ -304,7 +304,7 @@
     case GRPC_CHTTP2_FRAME_GOAWAY:
       return init_goaway_parser(t);
     default:
-      if (grpc_http_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
         gpr_log(GPR_ERROR, "Unknown frame type %02x", t->incoming_frame_type);
       }
       return init_skip_frame_parser(t, 0);
@@ -400,7 +400,7 @@
   grpc_chttp2_stream* s = t->incoming_stream;
   GPR_ASSERT(s != nullptr);
 
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     char* key = grpc_slice_to_c_string(GRPC_MDKEY(md));
     char* value =
         grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_HEX | GPR_DUMP_ASCII);
@@ -496,7 +496,7 @@
   grpc_chttp2_stream* s = t->incoming_stream;
   GPR_ASSERT(s != nullptr);
 
-  if (grpc_http_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
     char* key = grpc_slice_to_c_string(GRPC_MDKEY(md));
     char* value =
         grpc_dump_slice(GRPC_MDVALUE(md), GPR_DUMP_HEX | GPR_DUMP_ASCII);
@@ -761,7 +761,7 @@
   if (GPR_LIKELY(err == GRPC_ERROR_NONE)) {
     return err;
   } else if (grpc_error_get_int(err, GRPC_ERROR_INT_STREAM_ID, &unused)) {
-    if (grpc_http_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
       const char* msg = grpc_error_string(err);
       gpr_log(GPR_ERROR, "%s", msg);
     }
diff --git a/src/core/ext/transport/chttp2/transport/stream_lists.cc b/src/core/ext/transport/chttp2/transport/stream_lists.cc
index 6626170..db79899 100644
--- a/src/core/ext/transport/chttp2/transport/stream_lists.cc
+++ b/src/core/ext/transport/chttp2/transport/stream_lists.cc
@@ -67,7 +67,7 @@
     s->included[id] = 0;
   }
   *stream = s;
-  if (s && grpc_trace_http2_stream_state.enabled()) {
+  if (s && GRPC_TRACE_FLAG_ENABLED(grpc_trace_http2_stream_state)) {
     gpr_log(GPR_INFO, "%p[%d][%s]: pop from %s", t, s->id,
             t->is_client ? "cli" : "svr", stream_list_id_string(id));
   }
@@ -89,7 +89,7 @@
   } else {
     t->lists[id].tail = s->links[id].prev;
   }
-  if (grpc_trace_http2_stream_state.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_http2_stream_state)) {
     gpr_log(GPR_INFO, "%p[%d][%s]: remove from %s", t, s->id,
             t->is_client ? "cli" : "svr", stream_list_id_string(id));
   }
@@ -121,7 +121,7 @@
   }
   t->lists[id].tail = s;
   s->included[id] = 1;
-  if (grpc_trace_http2_stream_state.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_http2_stream_state)) {
     gpr_log(GPR_INFO, "%p[%d][%s]: add to %s", t, s->id,
             t->is_client ? "cli" : "svr", stream_list_id_string(id));
   }
diff --git a/src/core/ext/transport/chttp2/transport/writing.cc b/src/core/ext/transport/chttp2/transport/writing.cc
index bc8968a..f3cb390 100644
--- a/src/core/ext/transport/chttp2/transport/writing.cc
+++ b/src/core/ext/transport/chttp2/transport/writing.cc
@@ -25,6 +25,7 @@
 
 #include <grpc/support/log.h>
 
+#include "src/core/lib/compression/stream_compression.h"
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/profiling/timers.h"
 #include "src/core/lib/slice/slice_internal.h"
@@ -52,7 +53,8 @@
   }
   if (!grpc_closure_list_empty(pq->lists[GRPC_CHTTP2_PCL_INFLIGHT])) {
     /* ping already in-flight: wait */
-    if (grpc_http_trace.enabled() || grpc_bdp_estimator_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace) ||
+        GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
       gpr_log(GPR_INFO, "%s: Ping delayed [%p]: already pinging",
               t->is_client ? "CLIENT" : "SERVER", t->peer_string);
     }
@@ -61,7 +63,8 @@
   if (t->ping_state.pings_before_data_required == 0 &&
       t->ping_policy.max_pings_without_data != 0) {
     /* need to receive something of substance before sending a ping again */
-    if (grpc_http_trace.enabled() || grpc_bdp_estimator_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace) ||
+        GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
       gpr_log(GPR_INFO, "%s: Ping delayed [%p]: too many recent pings: %d/%d",
               t->is_client ? "CLIENT" : "SERVER", t->peer_string,
               t->ping_state.pings_before_data_required,
@@ -81,7 +84,8 @@
 
   if (next_allowed_ping > now) {
     /* not enough elapsed time between successive pings */
-    if (grpc_http_trace.enabled() || grpc_bdp_estimator_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace) ||
+        GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
       gpr_log(GPR_INFO,
               "%s: Ping delayed [%p]: not enough time elapsed since last ping. "
               " Last ping %f: Next ping %f: Now %f",
@@ -107,7 +111,8 @@
                         grpc_chttp2_ping_create(false, pq->inflight_id));
   GRPC_STATS_INC_HTTP2_PINGS_SENT();
   t->ping_state.last_ping_sent_time = now;
-  if (grpc_http_trace.enabled() || grpc_bdp_estimator_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace) ||
+      GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
     gpr_log(GPR_INFO, "%s: Ping sent [%s]: %d/%d",
             t->is_client ? "CLIENT" : "SERVER", t->peer_string,
             t->ping_state.pings_before_data_required,
@@ -140,7 +145,7 @@
 
 static void report_stall(grpc_chttp2_transport* t, grpc_chttp2_stream* s,
                          const char* staller) {
-  if (grpc_flowctl_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_flowctl_trace)) {
     gpr_log(
         GPR_DEBUG,
         "%s:%p stream %d moved to stalled list by %s. This is FULLY expected "
@@ -150,7 +155,11 @@
         ":flowed=%" PRId64 ":peer_initwin=%d:t_win=%" PRId64
         ":s_win=%d:s_delta=%" PRId64 "]",
         t->peer_string, t, s->id, staller, s->flow_controlled_buffer.length,
-        s->compressed_data_buffer.length, s->flow_controlled_bytes_flowed,
+        s->stream_compression_method ==
+                GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS
+            ? 0
+            : s->compressed_data_buffer.length,
+        s->flow_controlled_bytes_flowed,
         t->settings[GRPC_ACKED_SETTINGS]
                    [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE],
         t->flow_control->remote_window(),
@@ -163,15 +172,6 @@
   }
 }
 
-static bool stream_ref_if_not_destroyed(gpr_refcount* r) {
-  gpr_atm count;
-  do {
-    count = gpr_atm_acq_load(&r->count);
-    if (count == 0) return false;
-  } while (!gpr_atm_rel_cas(&r->count, count, count + 1));
-  return true;
-}
-
 /* How many bytes would we like to put on the wire during a single syscall */
 static uint32_t target_write_size(grpc_chttp2_transport* t) {
   return 1024 * 1024;
@@ -254,7 +254,7 @@
     while (grpc_chttp2_list_pop_stalled_by_transport(t_, &s)) {
       if (t_->closed_with_error == GRPC_ERROR_NONE &&
           grpc_chttp2_list_add_writable_stream(t_, s)) {
-        if (!stream_ref_if_not_destroyed(&s->refcount->refs)) {
+        if (!s->refcount->refs.RefIfNonZero()) {
           grpc_chttp2_list_remove_writable_stream(t_, s);
         }
       }
@@ -334,7 +334,23 @@
 
   bool AnyOutgoing() const { return max_outgoing() > 0; }
 
+  void FlushUncompressedBytes() {
+    uint32_t send_bytes = static_cast<uint32_t> GPR_MIN(
+        max_outgoing(), s_->flow_controlled_buffer.length);
+    is_last_frame_ = send_bytes == s_->flow_controlled_buffer.length &&
+                     s_->fetching_send_message == nullptr &&
+                     s_->send_trailing_metadata != nullptr &&
+                     grpc_metadata_batch_is_empty(s_->send_trailing_metadata);
+    grpc_chttp2_encode_data(s_->id, &s_->flow_controlled_buffer, send_bytes,
+                            is_last_frame_, &s_->stats.outgoing, &t_->outbuf);
+    s_->flow_control->SentData(send_bytes);
+    s_->sending_bytes += send_bytes;
+  }
+
   void FlushCompressedBytes() {
+    GPR_DEBUG_ASSERT(s_->stream_compression_method !=
+                     GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS);
+
     uint32_t send_bytes = static_cast<uint32_t> GPR_MIN(
         max_outgoing(), s_->compressed_data_buffer.length);
     bool is_last_data_frame =
@@ -369,6 +385,9 @@
   }
 
   void CompressMoreBytes() {
+    GPR_DEBUG_ASSERT(s_->stream_compression_method !=
+                     GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS);
+
     if (s_->stream_compression_ctx == nullptr) {
       s_->stream_compression_ctx =
           grpc_stream_compression_context_create(s_->stream_compression_method);
@@ -426,7 +445,7 @@
     // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#when-retries-are-valid
     if (!t_->is_client && s_->fetching_send_message == nullptr &&
         s_->flow_controlled_buffer.length == 0 &&
-        s_->compressed_data_buffer.length == 0 &&
+        compressed_data_buffer_len() == 0 &&
         s_->send_trailing_metadata != nullptr &&
         is_default_initial_metadata(s_->send_initial_metadata)) {
       ConvertInitialMetadataToTrailingMetadata();
@@ -455,6 +474,13 @@
         "send_initial_metadata_finished");
   }
 
+  bool compressed_data_buffer_len() {
+    return s_->stream_compression_method ==
+                   GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS
+               ? 0
+               : s_->compressed_data_buffer.length;
+  }
+
   void FlushWindowUpdates() {
     /* send any window updates */
     const uint32_t stream_announce = s_->flow_control->MaybeSendUpdate();
@@ -471,7 +497,7 @@
     if (!s_->sent_initial_metadata) return;
 
     if (s_->flow_controlled_buffer.length == 0 &&
-        s_->compressed_data_buffer.length == 0) {
+        compressed_data_buffer_len() == 0) {
       return;  // early out: nothing to do
     }
 
@@ -488,13 +514,21 @@
       return;  // early out: nothing to do
     }
 
-    while ((s_->flow_controlled_buffer.length > 0 ||
-            s_->compressed_data_buffer.length > 0) &&
-           data_send_context.max_outgoing() > 0) {
-      if (s_->compressed_data_buffer.length > 0) {
-        data_send_context.FlushCompressedBytes();
-      } else {
-        data_send_context.CompressMoreBytes();
+    if (s_->stream_compression_method ==
+        GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS) {
+      while (s_->flow_controlled_buffer.length > 0 &&
+             data_send_context.max_outgoing() > 0) {
+        data_send_context.FlushUncompressedBytes();
+      }
+    } else {
+      while ((s_->flow_controlled_buffer.length > 0 ||
+              s_->compressed_data_buffer.length > 0) &&
+             data_send_context.max_outgoing() > 0) {
+        if (s_->compressed_data_buffer.length > 0) {
+          data_send_context.FlushCompressedBytes();
+        } else {
+          data_send_context.CompressMoreBytes();
+        }
       }
     }
     write_context_->ResetPingClock();
@@ -504,7 +538,7 @@
     data_send_context.CallCallbacks();
     stream_became_writable_ = true;
     if (s_->flow_controlled_buffer.length > 0 ||
-        s_->compressed_data_buffer.length > 0) {
+        compressed_data_buffer_len() > 0) {
       GRPC_CHTTP2_STREAM_REF(s_, "chttp2_writing:fork");
       grpc_chttp2_list_add_writable_stream(t_, s_);
     }
@@ -517,7 +551,7 @@
     if (s_->send_trailing_metadata == nullptr) return;
     if (s_->fetching_send_message != nullptr) return;
     if (s_->flow_controlled_buffer.length != 0) return;
-    if (s_->compressed_data_buffer.length != 0) return;
+    if (compressed_data_buffer_len() != 0) return;
 
     GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "sending trailing_metadata"));
     if (grpc_metadata_batch_is_empty(s_->send_trailing_metadata)) {
diff --git a/src/core/ext/transport/cronet/transport/cronet_transport.cc b/src/core/ext/transport/cronet/transport/cronet_transport.cc
index 9551b4b..320b529 100644
--- a/src/core/ext/transport/cronet/transport/cronet_transport.cc
+++ b/src/core/ext/transport/cronet/transport/cronet_transport.cc
@@ -29,6 +29,7 @@
 #include "src/core/ext/transport/chttp2/transport/bin_encoder.h"
 #include "src/core/ext/transport/chttp2/transport/incoming_metadata.h"
 #include "src/core/ext/transport/cronet/transport/cronet_transport.h"
+#include "src/core/lib/debug/trace.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
@@ -45,14 +46,12 @@
 #define GRPC_HEADER_SIZE_IN_BYTES 5
 #define GRPC_FLUSH_READ_SIZE 4096
 
-#define CRONET_LOG(...)                          \
-  do {                                           \
-    if (grpc_cronet_trace) gpr_log(__VA_ARGS__); \
+grpc_core::TraceFlag grpc_cronet_trace(false, "cronet");
+#define CRONET_LOG(...)                                    \
+  do {                                                     \
+    if (grpc_cronet_trace.enabled()) gpr_log(__VA_ARGS__); \
   } while (0)
 
-/* TODO (makdharma): Hook up into the wider tracing mechanism */
-int grpc_cronet_trace = 0;
-
 enum e_op_result {
   ACTION_TAKEN_WITH_CALLBACK,
   ACTION_TAKEN_NO_CALLBACK,
@@ -111,7 +110,7 @@
 /* TODO (makdharma): reorder structure for memory efficiency per
    http://www.catb.org/esr/structure-packing/#_structure_reordering: */
 struct read_state {
-  read_state(gpr_arena* arena)
+  read_state(grpc_core::Arena* arena)
       : trailing_metadata(arena), initial_metadata(arena) {
     grpc_slice_buffer_init(&read_slice_buffer);
   }
@@ -145,7 +144,7 @@
 
 /* track state of one stream op */
 struct op_state {
-  op_state(gpr_arena* arena) : rs(arena) {}
+  op_state(grpc_core::Arena* arena) : rs(arena) {}
 
   bool state_op_done[OP_NUM_OPS] = {};
   bool state_callback_received[OP_NUM_OPS] = {};
@@ -177,7 +176,7 @@
   bool done = false;
   struct stream_obj* s; /* Pointer back to the stream object */
   /* next op_and_state in the linked list */
-  struct op_and_state* next;
+  struct op_and_state* next = nullptr;
 };
 
 struct op_storage {
@@ -187,10 +186,10 @@
 
 struct stream_obj {
   stream_obj(grpc_transport* gt, grpc_stream* gs,
-             grpc_stream_refcount* refcount, gpr_arena* arena);
+             grpc_stream_refcount* refcount, grpc_core::Arena* arena);
   ~stream_obj();
 
-  gpr_arena* arena;
+  grpc_core::Arena* arena;
   struct op_and_state* oas = nullptr;
   grpc_transport_stream_op_batch* curr_op = nullptr;
   grpc_cronet_transport* curr_ct;
@@ -324,7 +323,7 @@
 
 inline op_and_state::op_and_state(stream_obj* s,
                                   const grpc_transport_stream_op_batch& op)
-    : op(op), state(s->arena), s(s), next(s->storage.head) {}
+    : op(op), state(s->arena), s(s) {}
 
 /*
   Add a new stream op to op storage.
@@ -335,10 +334,8 @@
   /* add new op at the beginning of the linked list. The memory is freed
   in remove_from_storage */
   op_and_state* new_op = grpc_core::New<op_and_state>(s, *op);
-  // Pontential fix to crash on GPR_ASSERT(!curr->done)
-  // TODO (mxyan): check if this is indeed necessary.
-  new_op->done = false;
   gpr_mu_lock(&s->mu);
+  new_op->next = storage->head;
   storage->head = new_op;
   storage->num_pending_ops++;
   if (op->send_message) {
@@ -1371,7 +1368,8 @@
 */
 
 inline stream_obj::stream_obj(grpc_transport* gt, grpc_stream* gs,
-                              grpc_stream_refcount* refcount, gpr_arena* arena)
+                              grpc_stream_refcount* refcount,
+                              grpc_core::Arena* arena)
     : arena(arena),
       curr_ct(reinterpret_cast<grpc_cronet_transport*>(gt)),
       curr_gs(gs),
@@ -1390,7 +1388,7 @@
 
 static int init_stream(grpc_transport* gt, grpc_stream* gs,
                        grpc_stream_refcount* refcount, const void* server_data,
-                       gpr_arena* arena) {
+                       grpc_core::Arena* arena) {
   new (gs) stream_obj(gt, gs, refcount, arena);
   return 0;
 }
diff --git a/src/core/ext/transport/inproc/inproc_transport.cc b/src/core/ext/transport/inproc/inproc_transport.cc
index d46c24a..8da8985 100644
--- a/src/core/ext/transport/inproc/inproc_transport.cc
+++ b/src/core/ext/transport/inproc/inproc_transport.cc
@@ -35,9 +35,11 @@
 #include "src/core/lib/transport/error_utils.h"
 #include "src/core/lib/transport/transport_impl.h"
 
-#define INPROC_LOG(...)                                    \
-  do {                                                     \
-    if (grpc_inproc_trace.enabled()) gpr_log(__VA_ARGS__); \
+#define INPROC_LOG(...)                               \
+  do {                                                \
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_inproc_trace)) { \
+      gpr_log(__VA_ARGS__);                           \
+    }                                                 \
   } while (0)
 
 namespace {
@@ -120,7 +122,7 @@
 
 struct inproc_stream {
   inproc_stream(inproc_transport* t, grpc_stream_refcount* refcount,
-                const void* server_data, gpr_arena* arena)
+                const void* server_data, grpc_core::Arena* arena)
       : t(t), refs(refcount), arena(arena) {
     // Ref this stream right now for ctor and list.
     ref("inproc_init_stream:init");
@@ -250,7 +252,7 @@
   grpc_stream_refcount* refs;
   grpc_closure* closure_at_destroy = nullptr;
 
-  gpr_arena* arena;
+  grpc_core::Arena* arena;
 
   grpc_transport_stream_op_batch* send_message_op = nullptr;
   grpc_transport_stream_op_batch* send_trailing_md_op = nullptr;
@@ -296,7 +298,7 @@
                              const grpc_metadata_batch* metadata,
                              uint32_t flags, grpc_metadata_batch* out_md,
                              uint32_t* outflags, bool* markfilled) {
-  if (grpc_inproc_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_inproc_trace)) {
     log_metadata(metadata, s->t->is_client, outflags != nullptr);
   }
 
@@ -309,8 +311,8 @@
   grpc_error* error = GRPC_ERROR_NONE;
   for (grpc_linked_mdelem* elem = metadata->list.head;
        (elem != nullptr) && (error == GRPC_ERROR_NONE); elem = elem->next) {
-    grpc_linked_mdelem* nelem = static_cast<grpc_linked_mdelem*>(
-        gpr_arena_alloc(s->arena, sizeof(*nelem)));
+    grpc_linked_mdelem* nelem =
+        static_cast<grpc_linked_mdelem*>(s->arena->Alloc(sizeof(*nelem)));
     nelem->md =
         grpc_mdelem_from_slices(grpc_slice_intern(GRPC_MDKEY(elem->md)),
                                 grpc_slice_intern(GRPC_MDVALUE(elem->md)));
@@ -322,7 +324,7 @@
 
 int init_stream(grpc_transport* gt, grpc_stream* gs,
                 grpc_stream_refcount* refcount, const void* server_data,
-                gpr_arena* arena) {
+                grpc_core::Arena* arena) {
   INPROC_LOG(GPR_INFO, "init_stream %p %p %p", gt, gs, server_data);
   inproc_transport* t = reinterpret_cast<inproc_transport*>(gt);
   new (gs) inproc_stream(t, refcount, server_data, arena);
@@ -436,13 +438,13 @@
       // since it expects that as well as no error yet
       grpc_metadata_batch fake_md;
       grpc_metadata_batch_init(&fake_md);
-      grpc_linked_mdelem* path_md = static_cast<grpc_linked_mdelem*>(
-          gpr_arena_alloc(s->arena, sizeof(*path_md)));
+      grpc_linked_mdelem* path_md =
+          static_cast<grpc_linked_mdelem*>(s->arena->Alloc(sizeof(*path_md)));
       path_md->md = grpc_mdelem_from_slices(g_fake_path_key, g_fake_path_value);
       GPR_ASSERT(grpc_metadata_batch_link_tail(&fake_md, path_md) ==
                  GRPC_ERROR_NONE);
-      grpc_linked_mdelem* auth_md = static_cast<grpc_linked_mdelem*>(
-          gpr_arena_alloc(s->arena, sizeof(*auth_md)));
+      grpc_linked_mdelem* auth_md =
+          static_cast<grpc_linked_mdelem*>(s->arena->Alloc(sizeof(*auth_md)));
       auth_md->md = grpc_mdelem_from_slices(g_fake_auth_key, g_fake_auth_value);
       GPR_ASSERT(grpc_metadata_batch_link_tail(&fake_md, auth_md) ==
                  GRPC_ERROR_NONE);
@@ -907,7 +909,7 @@
   gpr_mu* mu = &s->t->mu->mu;  // save aside in case s gets closed
   gpr_mu_lock(mu);
 
-  if (grpc_inproc_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_inproc_trace)) {
     if (op->send_initial_metadata) {
       log_metadata(op->payload->send_initial_metadata.send_initial_metadata,
                    s->t->is_client, true);
@@ -1088,10 +1090,8 @@
 
 void close_transport_locked(inproc_transport* t) {
   INPROC_LOG(GPR_INFO, "close_transport %p %d", t, t->is_closed);
-  grpc_connectivity_state_set(
-      &t->connectivity, GRPC_CHANNEL_SHUTDOWN,
-      GRPC_ERROR_CREATE_FROM_STATIC_STRING("Closing transport."),
-      "close transport");
+  grpc_connectivity_state_set(&t->connectivity, GRPC_CHANNEL_SHUTDOWN,
+                              "close transport");
   if (!t->is_closed) {
     t->is_closed = true;
     /* Also end all streams on this transport */
diff --git a/src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.c b/src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.c
new file mode 100644
index 0000000..2af77c5
--- /dev/null
+++ b/src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.c
@@ -0,0 +1,105 @@
+/* This file was generated by upbc (the upb compiler) from the input
+ * file:
+ *
+ *     envoy/api/v2/endpoint/load_report.proto
+ *
+ * Do not edit -- your changes will be discarded when the file is
+ * regenerated. */
+
+#include <stddef.h>
+#include "upb/msg.h"
+#include "envoy/api/v2/endpoint/load_report.upb.h"
+#include "envoy/api/v2/core/address.upb.h"
+#include "envoy/api/v2/core/base.upb.h"
+#include "google/protobuf/duration.upb.h"
+#include "validate/validate.upb.h"
+#include "gogoproto/gogo.upb.h"
+
+#include "upb/port_def.inc"
+
+static const upb_msglayout *const envoy_api_v2_endpoint_UpstreamLocalityStats_submsgs[3] = {
+  &envoy_api_v2_core_Locality_msginit,
+  &envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit,
+  &envoy_api_v2_endpoint_UpstreamEndpointStats_msginit,
+};
+
+static const upb_msglayout_field envoy_api_v2_endpoint_UpstreamLocalityStats__fields[7] = {
+  {1, UPB_SIZE(28, 32), 0, 0, 11, 1},
+  {2, UPB_SIZE(0, 0), 0, 0, 4, 1},
+  {3, UPB_SIZE(8, 8), 0, 0, 4, 1},
+  {4, UPB_SIZE(16, 16), 0, 0, 4, 1},
+  {5, UPB_SIZE(32, 40), 0, 1, 11, 3},
+  {6, UPB_SIZE(24, 24), 0, 0, 13, 1},
+  {7, UPB_SIZE(36, 48), 0, 2, 11, 3},
+};
+
+const upb_msglayout envoy_api_v2_endpoint_UpstreamLocalityStats_msginit = {
+  &envoy_api_v2_endpoint_UpstreamLocalityStats_submsgs[0],
+  &envoy_api_v2_endpoint_UpstreamLocalityStats__fields[0],
+  UPB_SIZE(40, 56), 7, false,
+};
+
+static const upb_msglayout *const envoy_api_v2_endpoint_UpstreamEndpointStats_submsgs[2] = {
+  &envoy_api_v2_core_Address_msginit,
+  &envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit,
+};
+
+static const upb_msglayout_field envoy_api_v2_endpoint_UpstreamEndpointStats__fields[5] = {
+  {1, UPB_SIZE(24, 24), 0, 0, 11, 1},
+  {2, UPB_SIZE(0, 0), 0, 0, 4, 1},
+  {3, UPB_SIZE(8, 8), 0, 0, 4, 1},
+  {4, UPB_SIZE(16, 16), 0, 0, 4, 1},
+  {5, UPB_SIZE(28, 32), 0, 1, 11, 3},
+};
+
+const upb_msglayout envoy_api_v2_endpoint_UpstreamEndpointStats_msginit = {
+  &envoy_api_v2_endpoint_UpstreamEndpointStats_submsgs[0],
+  &envoy_api_v2_endpoint_UpstreamEndpointStats__fields[0],
+  UPB_SIZE(32, 40), 5, false,
+};
+
+static const upb_msglayout_field envoy_api_v2_endpoint_EndpointLoadMetricStats__fields[3] = {
+  {1, UPB_SIZE(16, 16), 0, 0, 9, 1},
+  {2, UPB_SIZE(0, 0), 0, 0, 4, 1},
+  {3, UPB_SIZE(8, 8), 0, 0, 1, 1},
+};
+
+const upb_msglayout envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit = {
+  NULL,
+  &envoy_api_v2_endpoint_EndpointLoadMetricStats__fields[0],
+  UPB_SIZE(24, 32), 3, false,
+};
+
+static const upb_msglayout *const envoy_api_v2_endpoint_ClusterStats_submsgs[3] = {
+  &envoy_api_v2_endpoint_ClusterStats_DroppedRequests_msginit,
+  &envoy_api_v2_endpoint_UpstreamLocalityStats_msginit,
+  &google_protobuf_Duration_msginit,
+};
+
+static const upb_msglayout_field envoy_api_v2_endpoint_ClusterStats__fields[5] = {
+  {1, UPB_SIZE(8, 8), 0, 0, 9, 1},
+  {2, UPB_SIZE(20, 32), 0, 1, 11, 3},
+  {3, UPB_SIZE(0, 0), 0, 0, 4, 1},
+  {4, UPB_SIZE(16, 24), 0, 2, 11, 1},
+  {5, UPB_SIZE(24, 40), 0, 0, 11, 3},
+};
+
+const upb_msglayout envoy_api_v2_endpoint_ClusterStats_msginit = {
+  &envoy_api_v2_endpoint_ClusterStats_submsgs[0],
+  &envoy_api_v2_endpoint_ClusterStats__fields[0],
+  UPB_SIZE(32, 48), 5, false,
+};
+
+static const upb_msglayout_field envoy_api_v2_endpoint_ClusterStats_DroppedRequests__fields[2] = {
+  {1, UPB_SIZE(8, 8), 0, 0, 9, 1},
+  {2, UPB_SIZE(0, 0), 0, 0, 4, 1},
+};
+
+const upb_msglayout envoy_api_v2_endpoint_ClusterStats_DroppedRequests_msginit = {
+  NULL,
+  &envoy_api_v2_endpoint_ClusterStats_DroppedRequests__fields[0],
+  UPB_SIZE(16, 32), 2, false,
+};
+
+#include "upb/port_undef.inc"
+
diff --git a/src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.h b/src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.h
new file mode 100644
index 0000000..7ee2129
--- /dev/null
+++ b/src/core/ext/upb-generated/envoy/api/v2/endpoint/load_report.upb.h
@@ -0,0 +1,299 @@
+/* This file was generated by upbc (the upb compiler) from the input
+ * file:
+ *
+ *     envoy/api/v2/endpoint/load_report.proto
+ *
+ * Do not edit -- your changes will be discarded when the file is
+ * regenerated. */
+
+#ifndef ENVOY_API_V2_ENDPOINT_LOAD_REPORT_PROTO_UPB_H_
+#define ENVOY_API_V2_ENDPOINT_LOAD_REPORT_PROTO_UPB_H_
+
+#include "upb/generated_util.h"
+
+#include "upb/msg.h"
+
+#include "upb/decode.h"
+#include "upb/encode.h"
+#include "upb/port_def.inc"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct envoy_api_v2_endpoint_UpstreamLocalityStats;
+struct envoy_api_v2_endpoint_UpstreamEndpointStats;
+struct envoy_api_v2_endpoint_EndpointLoadMetricStats;
+struct envoy_api_v2_endpoint_ClusterStats;
+struct envoy_api_v2_endpoint_ClusterStats_DroppedRequests;
+typedef struct envoy_api_v2_endpoint_UpstreamLocalityStats envoy_api_v2_endpoint_UpstreamLocalityStats;
+typedef struct envoy_api_v2_endpoint_UpstreamEndpointStats envoy_api_v2_endpoint_UpstreamEndpointStats;
+typedef struct envoy_api_v2_endpoint_EndpointLoadMetricStats envoy_api_v2_endpoint_EndpointLoadMetricStats;
+typedef struct envoy_api_v2_endpoint_ClusterStats envoy_api_v2_endpoint_ClusterStats;
+typedef struct envoy_api_v2_endpoint_ClusterStats_DroppedRequests envoy_api_v2_endpoint_ClusterStats_DroppedRequests;
+extern const upb_msglayout envoy_api_v2_endpoint_UpstreamLocalityStats_msginit;
+extern const upb_msglayout envoy_api_v2_endpoint_UpstreamEndpointStats_msginit;
+extern const upb_msglayout envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit;
+extern const upb_msglayout envoy_api_v2_endpoint_ClusterStats_msginit;
+extern const upb_msglayout envoy_api_v2_endpoint_ClusterStats_DroppedRequests_msginit;
+struct envoy_api_v2_core_Address;
+struct envoy_api_v2_core_Locality;
+struct google_protobuf_Duration;
+extern const upb_msglayout envoy_api_v2_core_Address_msginit;
+extern const upb_msglayout envoy_api_v2_core_Locality_msginit;
+extern const upb_msglayout google_protobuf_Duration_msginit;
+
+/* Enums */
+
+
+/* envoy.api.v2.endpoint.UpstreamLocalityStats */
+
+UPB_INLINE envoy_api_v2_endpoint_UpstreamLocalityStats *envoy_api_v2_endpoint_UpstreamLocalityStats_new(upb_arena *arena) {
+  return (envoy_api_v2_endpoint_UpstreamLocalityStats *)upb_msg_new(&envoy_api_v2_endpoint_UpstreamLocalityStats_msginit, arena);
+}
+UPB_INLINE envoy_api_v2_endpoint_UpstreamLocalityStats *envoy_api_v2_endpoint_UpstreamLocalityStats_parsenew(upb_strview buf, upb_arena *arena) {
+  envoy_api_v2_endpoint_UpstreamLocalityStats *ret = envoy_api_v2_endpoint_UpstreamLocalityStats_new(arena);
+  return (ret && upb_decode(buf, ret, &envoy_api_v2_endpoint_UpstreamLocalityStats_msginit)) ? ret : NULL;
+}
+UPB_INLINE char *envoy_api_v2_endpoint_UpstreamLocalityStats_serialize(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg, upb_arena *arena, size_t *len) {
+  return upb_encode(msg, &envoy_api_v2_endpoint_UpstreamLocalityStats_msginit, arena, len);
+}
+
+UPB_INLINE const struct envoy_api_v2_core_Locality* envoy_api_v2_endpoint_UpstreamLocalityStats_locality(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg) { return UPB_FIELD_AT(msg, const struct envoy_api_v2_core_Locality*, UPB_SIZE(28, 32)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_UpstreamLocalityStats_total_successful_requests(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_UpstreamLocalityStats_total_requests_in_progress(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(8, 8)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_UpstreamLocalityStats_total_error_requests(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(16, 16)); }
+UPB_INLINE const envoy_api_v2_endpoint_EndpointLoadMetricStats* const* envoy_api_v2_endpoint_UpstreamLocalityStats_load_metric_stats(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg, size_t *len) { return (const envoy_api_v2_endpoint_EndpointLoadMetricStats* const*)_upb_array_accessor(msg, UPB_SIZE(32, 40), len); }
+UPB_INLINE uint32_t envoy_api_v2_endpoint_UpstreamLocalityStats_priority(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg) { return UPB_FIELD_AT(msg, uint32_t, UPB_SIZE(24, 24)); }
+UPB_INLINE const envoy_api_v2_endpoint_UpstreamEndpointStats* const* envoy_api_v2_endpoint_UpstreamLocalityStats_upstream_endpoint_stats(const envoy_api_v2_endpoint_UpstreamLocalityStats *msg, size_t *len) { return (const envoy_api_v2_endpoint_UpstreamEndpointStats* const*)_upb_array_accessor(msg, UPB_SIZE(36, 48), len); }
+
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamLocalityStats_set_locality(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, struct envoy_api_v2_core_Locality* value) {
+  UPB_FIELD_AT(msg, struct envoy_api_v2_core_Locality*, UPB_SIZE(28, 32)) = value;
+}
+UPB_INLINE struct envoy_api_v2_core_Locality* envoy_api_v2_endpoint_UpstreamLocalityStats_mutable_locality(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, upb_arena *arena) {
+  struct envoy_api_v2_core_Locality* sub = (struct envoy_api_v2_core_Locality*)envoy_api_v2_endpoint_UpstreamLocalityStats_locality(msg);
+  if (sub == NULL) {
+    sub = (struct envoy_api_v2_core_Locality*)upb_msg_new(&envoy_api_v2_core_Locality_msginit, arena);
+    if (!sub) return NULL;
+    envoy_api_v2_endpoint_UpstreamLocalityStats_set_locality(msg, sub);
+  }
+  return sub;
+}
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamLocalityStats_set_total_successful_requests(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamLocalityStats_set_total_requests_in_progress(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(8, 8)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamLocalityStats_set_total_error_requests(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(16, 16)) = value;
+}
+UPB_INLINE envoy_api_v2_endpoint_EndpointLoadMetricStats** envoy_api_v2_endpoint_UpstreamLocalityStats_mutable_load_metric_stats(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, size_t *len) {
+  return (envoy_api_v2_endpoint_EndpointLoadMetricStats**)_upb_array_mutable_accessor(msg, UPB_SIZE(32, 40), len);
+}
+UPB_INLINE envoy_api_v2_endpoint_EndpointLoadMetricStats** envoy_api_v2_endpoint_UpstreamLocalityStats_resize_load_metric_stats(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, size_t len, upb_arena *arena) {
+  return (envoy_api_v2_endpoint_EndpointLoadMetricStats**)_upb_array_resize_accessor(msg, UPB_SIZE(32, 40), len, UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, arena);
+}
+UPB_INLINE struct envoy_api_v2_endpoint_EndpointLoadMetricStats* envoy_api_v2_endpoint_UpstreamLocalityStats_add_load_metric_stats(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, upb_arena *arena) {
+  struct envoy_api_v2_endpoint_EndpointLoadMetricStats* sub = (struct envoy_api_v2_endpoint_EndpointLoadMetricStats*)upb_msg_new(&envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit, arena);
+  bool ok = _upb_array_append_accessor(
+      msg, UPB_SIZE(32, 40), UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, &sub, arena);
+  if (!ok) return NULL;
+  return sub;
+}
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamLocalityStats_set_priority(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, uint32_t value) {
+  UPB_FIELD_AT(msg, uint32_t, UPB_SIZE(24, 24)) = value;
+}
+UPB_INLINE envoy_api_v2_endpoint_UpstreamEndpointStats** envoy_api_v2_endpoint_UpstreamLocalityStats_mutable_upstream_endpoint_stats(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, size_t *len) {
+  return (envoy_api_v2_endpoint_UpstreamEndpointStats**)_upb_array_mutable_accessor(msg, UPB_SIZE(36, 48), len);
+}
+UPB_INLINE envoy_api_v2_endpoint_UpstreamEndpointStats** envoy_api_v2_endpoint_UpstreamLocalityStats_resize_upstream_endpoint_stats(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, size_t len, upb_arena *arena) {
+  return (envoy_api_v2_endpoint_UpstreamEndpointStats**)_upb_array_resize_accessor(msg, UPB_SIZE(36, 48), len, UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, arena);
+}
+UPB_INLINE struct envoy_api_v2_endpoint_UpstreamEndpointStats* envoy_api_v2_endpoint_UpstreamLocalityStats_add_upstream_endpoint_stats(envoy_api_v2_endpoint_UpstreamLocalityStats *msg, upb_arena *arena) {
+  struct envoy_api_v2_endpoint_UpstreamEndpointStats* sub = (struct envoy_api_v2_endpoint_UpstreamEndpointStats*)upb_msg_new(&envoy_api_v2_endpoint_UpstreamEndpointStats_msginit, arena);
+  bool ok = _upb_array_append_accessor(
+      msg, UPB_SIZE(36, 48), UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, &sub, arena);
+  if (!ok) return NULL;
+  return sub;
+}
+
+
+/* envoy.api.v2.endpoint.UpstreamEndpointStats */
+
+UPB_INLINE envoy_api_v2_endpoint_UpstreamEndpointStats *envoy_api_v2_endpoint_UpstreamEndpointStats_new(upb_arena *arena) {
+  return (envoy_api_v2_endpoint_UpstreamEndpointStats *)upb_msg_new(&envoy_api_v2_endpoint_UpstreamEndpointStats_msginit, arena);
+}
+UPB_INLINE envoy_api_v2_endpoint_UpstreamEndpointStats *envoy_api_v2_endpoint_UpstreamEndpointStats_parsenew(upb_strview buf, upb_arena *arena) {
+  envoy_api_v2_endpoint_UpstreamEndpointStats *ret = envoy_api_v2_endpoint_UpstreamEndpointStats_new(arena);
+  return (ret && upb_decode(buf, ret, &envoy_api_v2_endpoint_UpstreamEndpointStats_msginit)) ? ret : NULL;
+}
+UPB_INLINE char *envoy_api_v2_endpoint_UpstreamEndpointStats_serialize(const envoy_api_v2_endpoint_UpstreamEndpointStats *msg, upb_arena *arena, size_t *len) {
+  return upb_encode(msg, &envoy_api_v2_endpoint_UpstreamEndpointStats_msginit, arena, len);
+}
+
+UPB_INLINE const struct envoy_api_v2_core_Address* envoy_api_v2_endpoint_UpstreamEndpointStats_address(const envoy_api_v2_endpoint_UpstreamEndpointStats *msg) { return UPB_FIELD_AT(msg, const struct envoy_api_v2_core_Address*, UPB_SIZE(24, 24)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_UpstreamEndpointStats_total_successful_requests(const envoy_api_v2_endpoint_UpstreamEndpointStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_UpstreamEndpointStats_total_requests_in_progress(const envoy_api_v2_endpoint_UpstreamEndpointStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(8, 8)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_UpstreamEndpointStats_total_error_requests(const envoy_api_v2_endpoint_UpstreamEndpointStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(16, 16)); }
+UPB_INLINE const envoy_api_v2_endpoint_EndpointLoadMetricStats* const* envoy_api_v2_endpoint_UpstreamEndpointStats_load_metric_stats(const envoy_api_v2_endpoint_UpstreamEndpointStats *msg, size_t *len) { return (const envoy_api_v2_endpoint_EndpointLoadMetricStats* const*)_upb_array_accessor(msg, UPB_SIZE(28, 32), len); }
+
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamEndpointStats_set_address(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, struct envoy_api_v2_core_Address* value) {
+  UPB_FIELD_AT(msg, struct envoy_api_v2_core_Address*, UPB_SIZE(24, 24)) = value;
+}
+UPB_INLINE struct envoy_api_v2_core_Address* envoy_api_v2_endpoint_UpstreamEndpointStats_mutable_address(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, upb_arena *arena) {
+  struct envoy_api_v2_core_Address* sub = (struct envoy_api_v2_core_Address*)envoy_api_v2_endpoint_UpstreamEndpointStats_address(msg);
+  if (sub == NULL) {
+    sub = (struct envoy_api_v2_core_Address*)upb_msg_new(&envoy_api_v2_core_Address_msginit, arena);
+    if (!sub) return NULL;
+    envoy_api_v2_endpoint_UpstreamEndpointStats_set_address(msg, sub);
+  }
+  return sub;
+}
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamEndpointStats_set_total_successful_requests(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamEndpointStats_set_total_requests_in_progress(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(8, 8)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_UpstreamEndpointStats_set_total_error_requests(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(16, 16)) = value;
+}
+UPB_INLINE envoy_api_v2_endpoint_EndpointLoadMetricStats** envoy_api_v2_endpoint_UpstreamEndpointStats_mutable_load_metric_stats(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, size_t *len) {
+  return (envoy_api_v2_endpoint_EndpointLoadMetricStats**)_upb_array_mutable_accessor(msg, UPB_SIZE(28, 32), len);
+}
+UPB_INLINE envoy_api_v2_endpoint_EndpointLoadMetricStats** envoy_api_v2_endpoint_UpstreamEndpointStats_resize_load_metric_stats(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, size_t len, upb_arena *arena) {
+  return (envoy_api_v2_endpoint_EndpointLoadMetricStats**)_upb_array_resize_accessor(msg, UPB_SIZE(28, 32), len, UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, arena);
+}
+UPB_INLINE struct envoy_api_v2_endpoint_EndpointLoadMetricStats* envoy_api_v2_endpoint_UpstreamEndpointStats_add_load_metric_stats(envoy_api_v2_endpoint_UpstreamEndpointStats *msg, upb_arena *arena) {
+  struct envoy_api_v2_endpoint_EndpointLoadMetricStats* sub = (struct envoy_api_v2_endpoint_EndpointLoadMetricStats*)upb_msg_new(&envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit, arena);
+  bool ok = _upb_array_append_accessor(
+      msg, UPB_SIZE(28, 32), UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, &sub, arena);
+  if (!ok) return NULL;
+  return sub;
+}
+
+
+/* envoy.api.v2.endpoint.EndpointLoadMetricStats */
+
+UPB_INLINE envoy_api_v2_endpoint_EndpointLoadMetricStats *envoy_api_v2_endpoint_EndpointLoadMetricStats_new(upb_arena *arena) {
+  return (envoy_api_v2_endpoint_EndpointLoadMetricStats *)upb_msg_new(&envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit, arena);
+}
+UPB_INLINE envoy_api_v2_endpoint_EndpointLoadMetricStats *envoy_api_v2_endpoint_EndpointLoadMetricStats_parsenew(upb_strview buf, upb_arena *arena) {
+  envoy_api_v2_endpoint_EndpointLoadMetricStats *ret = envoy_api_v2_endpoint_EndpointLoadMetricStats_new(arena);
+  return (ret && upb_decode(buf, ret, &envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit)) ? ret : NULL;
+}
+UPB_INLINE char *envoy_api_v2_endpoint_EndpointLoadMetricStats_serialize(const envoy_api_v2_endpoint_EndpointLoadMetricStats *msg, upb_arena *arena, size_t *len) {
+  return upb_encode(msg, &envoy_api_v2_endpoint_EndpointLoadMetricStats_msginit, arena, len);
+}
+
+UPB_INLINE upb_strview envoy_api_v2_endpoint_EndpointLoadMetricStats_metric_name(const envoy_api_v2_endpoint_EndpointLoadMetricStats *msg) { return UPB_FIELD_AT(msg, upb_strview, UPB_SIZE(16, 16)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_EndpointLoadMetricStats_num_requests_finished_with_metric(const envoy_api_v2_endpoint_EndpointLoadMetricStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)); }
+UPB_INLINE double envoy_api_v2_endpoint_EndpointLoadMetricStats_total_metric_value(const envoy_api_v2_endpoint_EndpointLoadMetricStats *msg) { return UPB_FIELD_AT(msg, double, UPB_SIZE(8, 8)); }
+
+UPB_INLINE void envoy_api_v2_endpoint_EndpointLoadMetricStats_set_metric_name(envoy_api_v2_endpoint_EndpointLoadMetricStats *msg, upb_strview value) {
+  UPB_FIELD_AT(msg, upb_strview, UPB_SIZE(16, 16)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_EndpointLoadMetricStats_set_num_requests_finished_with_metric(envoy_api_v2_endpoint_EndpointLoadMetricStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_EndpointLoadMetricStats_set_total_metric_value(envoy_api_v2_endpoint_EndpointLoadMetricStats *msg, double value) {
+  UPB_FIELD_AT(msg, double, UPB_SIZE(8, 8)) = value;
+}
+
+
+/* envoy.api.v2.endpoint.ClusterStats */
+
+UPB_INLINE envoy_api_v2_endpoint_ClusterStats *envoy_api_v2_endpoint_ClusterStats_new(upb_arena *arena) {
+  return (envoy_api_v2_endpoint_ClusterStats *)upb_msg_new(&envoy_api_v2_endpoint_ClusterStats_msginit, arena);
+}
+UPB_INLINE envoy_api_v2_endpoint_ClusterStats *envoy_api_v2_endpoint_ClusterStats_parsenew(upb_strview buf, upb_arena *arena) {
+  envoy_api_v2_endpoint_ClusterStats *ret = envoy_api_v2_endpoint_ClusterStats_new(arena);
+  return (ret && upb_decode(buf, ret, &envoy_api_v2_endpoint_ClusterStats_msginit)) ? ret : NULL;
+}
+UPB_INLINE char *envoy_api_v2_endpoint_ClusterStats_serialize(const envoy_api_v2_endpoint_ClusterStats *msg, upb_arena *arena, size_t *len) {
+  return upb_encode(msg, &envoy_api_v2_endpoint_ClusterStats_msginit, arena, len);
+}
+
+UPB_INLINE upb_strview envoy_api_v2_endpoint_ClusterStats_cluster_name(const envoy_api_v2_endpoint_ClusterStats *msg) { return UPB_FIELD_AT(msg, upb_strview, UPB_SIZE(8, 8)); }
+UPB_INLINE const envoy_api_v2_endpoint_UpstreamLocalityStats* const* envoy_api_v2_endpoint_ClusterStats_upstream_locality_stats(const envoy_api_v2_endpoint_ClusterStats *msg, size_t *len) { return (const envoy_api_v2_endpoint_UpstreamLocalityStats* const*)_upb_array_accessor(msg, UPB_SIZE(20, 32), len); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_ClusterStats_total_dropped_requests(const envoy_api_v2_endpoint_ClusterStats *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)); }
+UPB_INLINE const struct google_protobuf_Duration* envoy_api_v2_endpoint_ClusterStats_load_report_interval(const envoy_api_v2_endpoint_ClusterStats *msg) { return UPB_FIELD_AT(msg, const struct google_protobuf_Duration*, UPB_SIZE(16, 24)); }
+UPB_INLINE const envoy_api_v2_endpoint_ClusterStats_DroppedRequests* const* envoy_api_v2_endpoint_ClusterStats_dropped_requests(const envoy_api_v2_endpoint_ClusterStats *msg, size_t *len) { return (const envoy_api_v2_endpoint_ClusterStats_DroppedRequests* const*)_upb_array_accessor(msg, UPB_SIZE(24, 40), len); }
+
+UPB_INLINE void envoy_api_v2_endpoint_ClusterStats_set_cluster_name(envoy_api_v2_endpoint_ClusterStats *msg, upb_strview value) {
+  UPB_FIELD_AT(msg, upb_strview, UPB_SIZE(8, 8)) = value;
+}
+UPB_INLINE envoy_api_v2_endpoint_UpstreamLocalityStats** envoy_api_v2_endpoint_ClusterStats_mutable_upstream_locality_stats(envoy_api_v2_endpoint_ClusterStats *msg, size_t *len) {
+  return (envoy_api_v2_endpoint_UpstreamLocalityStats**)_upb_array_mutable_accessor(msg, UPB_SIZE(20, 32), len);
+}
+UPB_INLINE envoy_api_v2_endpoint_UpstreamLocalityStats** envoy_api_v2_endpoint_ClusterStats_resize_upstream_locality_stats(envoy_api_v2_endpoint_ClusterStats *msg, size_t len, upb_arena *arena) {
+  return (envoy_api_v2_endpoint_UpstreamLocalityStats**)_upb_array_resize_accessor(msg, UPB_SIZE(20, 32), len, UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, arena);
+}
+UPB_INLINE struct envoy_api_v2_endpoint_UpstreamLocalityStats* envoy_api_v2_endpoint_ClusterStats_add_upstream_locality_stats(envoy_api_v2_endpoint_ClusterStats *msg, upb_arena *arena) {
+  struct envoy_api_v2_endpoint_UpstreamLocalityStats* sub = (struct envoy_api_v2_endpoint_UpstreamLocalityStats*)upb_msg_new(&envoy_api_v2_endpoint_UpstreamLocalityStats_msginit, arena);
+  bool ok = _upb_array_append_accessor(
+      msg, UPB_SIZE(20, 32), UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, &sub, arena);
+  if (!ok) return NULL;
+  return sub;
+}
+UPB_INLINE void envoy_api_v2_endpoint_ClusterStats_set_total_dropped_requests(envoy_api_v2_endpoint_ClusterStats *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_ClusterStats_set_load_report_interval(envoy_api_v2_endpoint_ClusterStats *msg, struct google_protobuf_Duration* value) {
+  UPB_FIELD_AT(msg, struct google_protobuf_Duration*, UPB_SIZE(16, 24)) = value;
+}
+UPB_INLINE struct google_protobuf_Duration* envoy_api_v2_endpoint_ClusterStats_mutable_load_report_interval(envoy_api_v2_endpoint_ClusterStats *msg, upb_arena *arena) {
+  struct google_protobuf_Duration* sub = (struct google_protobuf_Duration*)envoy_api_v2_endpoint_ClusterStats_load_report_interval(msg);
+  if (sub == NULL) {
+    sub = (struct google_protobuf_Duration*)upb_msg_new(&google_protobuf_Duration_msginit, arena);
+    if (!sub) return NULL;
+    envoy_api_v2_endpoint_ClusterStats_set_load_report_interval(msg, sub);
+  }
+  return sub;
+}
+UPB_INLINE envoy_api_v2_endpoint_ClusterStats_DroppedRequests** envoy_api_v2_endpoint_ClusterStats_mutable_dropped_requests(envoy_api_v2_endpoint_ClusterStats *msg, size_t *len) {
+  return (envoy_api_v2_endpoint_ClusterStats_DroppedRequests**)_upb_array_mutable_accessor(msg, UPB_SIZE(24, 40), len);
+}
+UPB_INLINE envoy_api_v2_endpoint_ClusterStats_DroppedRequests** envoy_api_v2_endpoint_ClusterStats_resize_dropped_requests(envoy_api_v2_endpoint_ClusterStats *msg, size_t len, upb_arena *arena) {
+  return (envoy_api_v2_endpoint_ClusterStats_DroppedRequests**)_upb_array_resize_accessor(msg, UPB_SIZE(24, 40), len, UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, arena);
+}
+UPB_INLINE struct envoy_api_v2_endpoint_ClusterStats_DroppedRequests* envoy_api_v2_endpoint_ClusterStats_add_dropped_requests(envoy_api_v2_endpoint_ClusterStats *msg, upb_arena *arena) {
+  struct envoy_api_v2_endpoint_ClusterStats_DroppedRequests* sub = (struct envoy_api_v2_endpoint_ClusterStats_DroppedRequests*)upb_msg_new(&envoy_api_v2_endpoint_ClusterStats_DroppedRequests_msginit, arena);
+  bool ok = _upb_array_append_accessor(
+      msg, UPB_SIZE(24, 40), UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, &sub, arena);
+  if (!ok) return NULL;
+  return sub;
+}
+
+
+/* envoy.api.v2.endpoint.ClusterStats.DroppedRequests */
+
+UPB_INLINE envoy_api_v2_endpoint_ClusterStats_DroppedRequests *envoy_api_v2_endpoint_ClusterStats_DroppedRequests_new(upb_arena *arena) {
+  return (envoy_api_v2_endpoint_ClusterStats_DroppedRequests *)upb_msg_new(&envoy_api_v2_endpoint_ClusterStats_DroppedRequests_msginit, arena);
+}
+UPB_INLINE envoy_api_v2_endpoint_ClusterStats_DroppedRequests *envoy_api_v2_endpoint_ClusterStats_DroppedRequests_parsenew(upb_strview buf, upb_arena *arena) {
+  envoy_api_v2_endpoint_ClusterStats_DroppedRequests *ret = envoy_api_v2_endpoint_ClusterStats_DroppedRequests_new(arena);
+  return (ret && upb_decode(buf, ret, &envoy_api_v2_endpoint_ClusterStats_DroppedRequests_msginit)) ? ret : NULL;
+}
+UPB_INLINE char *envoy_api_v2_endpoint_ClusterStats_DroppedRequests_serialize(const envoy_api_v2_endpoint_ClusterStats_DroppedRequests *msg, upb_arena *arena, size_t *len) {
+  return upb_encode(msg, &envoy_api_v2_endpoint_ClusterStats_DroppedRequests_msginit, arena, len);
+}
+
+UPB_INLINE upb_strview envoy_api_v2_endpoint_ClusterStats_DroppedRequests_category(const envoy_api_v2_endpoint_ClusterStats_DroppedRequests *msg) { return UPB_FIELD_AT(msg, upb_strview, UPB_SIZE(8, 8)); }
+UPB_INLINE uint64_t envoy_api_v2_endpoint_ClusterStats_DroppedRequests_dropped_count(const envoy_api_v2_endpoint_ClusterStats_DroppedRequests *msg) { return UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)); }
+
+UPB_INLINE void envoy_api_v2_endpoint_ClusterStats_DroppedRequests_set_category(envoy_api_v2_endpoint_ClusterStats_DroppedRequests *msg, upb_strview value) {
+  UPB_FIELD_AT(msg, upb_strview, UPB_SIZE(8, 8)) = value;
+}
+UPB_INLINE void envoy_api_v2_endpoint_ClusterStats_DroppedRequests_set_dropped_count(envoy_api_v2_endpoint_ClusterStats_DroppedRequests *msg, uint64_t value) {
+  UPB_FIELD_AT(msg, uint64_t, UPB_SIZE(0, 0)) = value;
+}
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#include "upb/port_undef.inc"
+
+#endif  /* ENVOY_API_V2_ENDPOINT_LOAD_REPORT_PROTO_UPB_H_ */
diff --git a/src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.c b/src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.c
new file mode 100644
index 0000000..a6e4bd2
--- /dev/null
+++ b/src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.c
@@ -0,0 +1,52 @@
+/* This file was generated by upbc (the upb compiler) from the input
+ * file:
+ *
+ *     envoy/service/load_stats/v2/lrs.proto
+ *
+ * Do not edit -- your changes will be discarded when the file is
+ * regenerated. */
+
+#include <stddef.h>
+#include "upb/msg.h"
+#include "envoy/service/load_stats/v2/lrs.upb.h"
+#include "envoy/api/v2/core/base.upb.h"
+#include "envoy/api/v2/endpoint/load_report.upb.h"
+#include "google/protobuf/duration.upb.h"
+#include "validate/validate.upb.h"
+
+#include "upb/port_def.inc"
+
+static const upb_msglayout *const envoy_service_load_stats_v2_LoadStatsRequest_submsgs[2] = {
+  &envoy_api_v2_core_Node_msginit,
+  &envoy_api_v2_endpoint_ClusterStats_msginit,
+};
+
+static const upb_msglayout_field envoy_service_load_stats_v2_LoadStatsRequest__fields[2] = {
+  {1, UPB_SIZE(0, 0), 0, 0, 11, 1},
+  {2, UPB_SIZE(4, 8), 0, 1, 11, 3},
+};
+
+const upb_msglayout envoy_service_load_stats_v2_LoadStatsRequest_msginit = {
+  &envoy_service_load_stats_v2_LoadStatsRequest_submsgs[0],
+  &envoy_service_load_stats_v2_LoadStatsRequest__fields[0],
+  UPB_SIZE(8, 16), 2, false,
+};
+
+static const upb_msglayout *const envoy_service_load_stats_v2_LoadStatsResponse_submsgs[1] = {
+  &google_protobuf_Duration_msginit,
+};
+
+static const upb_msglayout_field envoy_service_load_stats_v2_LoadStatsResponse__fields[3] = {
+  {1, UPB_SIZE(8, 16), 0, 0, 9, 3},
+  {2, UPB_SIZE(4, 8), 0, 0, 11, 1},
+  {3, UPB_SIZE(0, 0), 0, 0, 8, 1},
+};
+
+const upb_msglayout envoy_service_load_stats_v2_LoadStatsResponse_msginit = {
+  &envoy_service_load_stats_v2_LoadStatsResponse_submsgs[0],
+  &envoy_service_load_stats_v2_LoadStatsResponse__fields[0],
+  UPB_SIZE(12, 24), 3, false,
+};
+
+#include "upb/port_undef.inc"
+
diff --git a/src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.h b/src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.h
new file mode 100644
index 0000000..99db767
--- /dev/null
+++ b/src/core/ext/upb-generated/envoy/service/load_stats/v2/lrs.upb.h
@@ -0,0 +1,132 @@
+/* This file was generated by upbc (the upb compiler) from the input
+ * file:
+ *
+ *     envoy/service/load_stats/v2/lrs.proto
+ *
+ * Do not edit -- your changes will be discarded when the file is
+ * regenerated. */
+
+#ifndef ENVOY_SERVICE_LOAD_STATS_V2_LRS_PROTO_UPB_H_
+#define ENVOY_SERVICE_LOAD_STATS_V2_LRS_PROTO_UPB_H_
+
+#include "upb/generated_util.h"
+
+#include "upb/msg.h"
+
+#include "upb/decode.h"
+#include "upb/encode.h"
+#include "upb/port_def.inc"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct envoy_service_load_stats_v2_LoadStatsRequest;
+struct envoy_service_load_stats_v2_LoadStatsResponse;
+typedef struct envoy_service_load_stats_v2_LoadStatsRequest envoy_service_load_stats_v2_LoadStatsRequest;
+typedef struct envoy_service_load_stats_v2_LoadStatsResponse envoy_service_load_stats_v2_LoadStatsResponse;
+extern const upb_msglayout envoy_service_load_stats_v2_LoadStatsRequest_msginit;
+extern const upb_msglayout envoy_service_load_stats_v2_LoadStatsResponse_msginit;
+struct envoy_api_v2_core_Node;
+struct envoy_api_v2_endpoint_ClusterStats;
+struct google_protobuf_Duration;
+extern const upb_msglayout envoy_api_v2_core_Node_msginit;
+extern const upb_msglayout envoy_api_v2_endpoint_ClusterStats_msginit;
+extern const upb_msglayout google_protobuf_Duration_msginit;
+
+/* Enums */
+
+
+/* envoy.service.load_stats.v2.LoadStatsRequest */
+
+UPB_INLINE envoy_service_load_stats_v2_LoadStatsRequest *envoy_service_load_stats_v2_LoadStatsRequest_new(upb_arena *arena) {
+  return (envoy_service_load_stats_v2_LoadStatsRequest *)upb_msg_new(&envoy_service_load_stats_v2_LoadStatsRequest_msginit, arena);
+}
+UPB_INLINE envoy_service_load_stats_v2_LoadStatsRequest *envoy_service_load_stats_v2_LoadStatsRequest_parsenew(upb_strview buf, upb_arena *arena) {
+  envoy_service_load_stats_v2_LoadStatsRequest *ret = envoy_service_load_stats_v2_LoadStatsRequest_new(arena);
+  return (ret && upb_decode(buf, ret, &envoy_service_load_stats_v2_LoadStatsRequest_msginit)) ? ret : NULL;
+}
+UPB_INLINE char *envoy_service_load_stats_v2_LoadStatsRequest_serialize(const envoy_service_load_stats_v2_LoadStatsRequest *msg, upb_arena *arena, size_t *len) {
+  return upb_encode(msg, &envoy_service_load_stats_v2_LoadStatsRequest_msginit, arena, len);
+}
+
+UPB_INLINE const struct envoy_api_v2_core_Node* envoy_service_load_stats_v2_LoadStatsRequest_node(const envoy_service_load_stats_v2_LoadStatsRequest *msg) { return UPB_FIELD_AT(msg, const struct envoy_api_v2_core_Node*, UPB_SIZE(0, 0)); }
+UPB_INLINE const struct envoy_api_v2_endpoint_ClusterStats* const* envoy_service_load_stats_v2_LoadStatsRequest_cluster_stats(const envoy_service_load_stats_v2_LoadStatsRequest *msg, size_t *len) { return (const struct envoy_api_v2_endpoint_ClusterStats* const*)_upb_array_accessor(msg, UPB_SIZE(4, 8), len); }
+
+UPB_INLINE void envoy_service_load_stats_v2_LoadStatsRequest_set_node(envoy_service_load_stats_v2_LoadStatsRequest *msg, struct envoy_api_v2_core_Node* value) {
+  UPB_FIELD_AT(msg, struct envoy_api_v2_core_Node*, UPB_SIZE(0, 0)) = value;
+}
+UPB_INLINE struct envoy_api_v2_core_Node* envoy_service_load_stats_v2_LoadStatsRequest_mutable_node(envoy_service_load_stats_v2_LoadStatsRequest *msg, upb_arena *arena) {
+  struct envoy_api_v2_core_Node* sub = (struct envoy_api_v2_core_Node*)envoy_service_load_stats_v2_LoadStatsRequest_node(msg);
+  if (sub == NULL) {
+    sub = (struct envoy_api_v2_core_Node*)upb_msg_new(&envoy_api_v2_core_Node_msginit, arena);
+    if (!sub) return NULL;
+    envoy_service_load_stats_v2_LoadStatsRequest_set_node(msg, sub);
+  }
+  return sub;
+}
+UPB_INLINE struct envoy_api_v2_endpoint_ClusterStats** envoy_service_load_stats_v2_LoadStatsRequest_mutable_cluster_stats(envoy_service_load_stats_v2_LoadStatsRequest *msg, size_t *len) {
+  return (struct envoy_api_v2_endpoint_ClusterStats**)_upb_array_mutable_accessor(msg, UPB_SIZE(4, 8), len);
+}
+UPB_INLINE struct envoy_api_v2_endpoint_ClusterStats** envoy_service_load_stats_v2_LoadStatsRequest_resize_cluster_stats(envoy_service_load_stats_v2_LoadStatsRequest *msg, size_t len, upb_arena *arena) {
+  return (struct envoy_api_v2_endpoint_ClusterStats**)_upb_array_resize_accessor(msg, UPB_SIZE(4, 8), len, UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, arena);
+}
+UPB_INLINE struct envoy_api_v2_endpoint_ClusterStats* envoy_service_load_stats_v2_LoadStatsRequest_add_cluster_stats(envoy_service_load_stats_v2_LoadStatsRequest *msg, upb_arena *arena) {
+  struct envoy_api_v2_endpoint_ClusterStats* sub = (struct envoy_api_v2_endpoint_ClusterStats*)upb_msg_new(&envoy_api_v2_endpoint_ClusterStats_msginit, arena);
+  bool ok = _upb_array_append_accessor(
+      msg, UPB_SIZE(4, 8), UPB_SIZE(4, 8), UPB_TYPE_MESSAGE, &sub, arena);
+  if (!ok) return NULL;
+  return sub;
+}
+
+
+/* envoy.service.load_stats.v2.LoadStatsResponse */
+
+UPB_INLINE envoy_service_load_stats_v2_LoadStatsResponse *envoy_service_load_stats_v2_LoadStatsResponse_new(upb_arena *arena) {
+  return (envoy_service_load_stats_v2_LoadStatsResponse *)upb_msg_new(&envoy_service_load_stats_v2_LoadStatsResponse_msginit, arena);
+}
+UPB_INLINE envoy_service_load_stats_v2_LoadStatsResponse *envoy_service_load_stats_v2_LoadStatsResponse_parsenew(upb_strview buf, upb_arena *arena) {
+  envoy_service_load_stats_v2_LoadStatsResponse *ret = envoy_service_load_stats_v2_LoadStatsResponse_new(arena);
+  return (ret && upb_decode(buf, ret, &envoy_service_load_stats_v2_LoadStatsResponse_msginit)) ? ret : NULL;
+}
+UPB_INLINE char *envoy_service_load_stats_v2_LoadStatsResponse_serialize(const envoy_service_load_stats_v2_LoadStatsResponse *msg, upb_arena *arena, size_t *len) {
+  return upb_encode(msg, &envoy_service_load_stats_v2_LoadStatsResponse_msginit, arena, len);
+}
+
+UPB_INLINE upb_strview const* envoy_service_load_stats_v2_LoadStatsResponse_clusters(const envoy_service_load_stats_v2_LoadStatsResponse *msg, size_t *len) { return (upb_strview const*)_upb_array_accessor(msg, UPB_SIZE(8, 16), len); }
+UPB_INLINE const struct google_protobuf_Duration* envoy_service_load_stats_v2_LoadStatsResponse_load_reporting_interval(const envoy_service_load_stats_v2_LoadStatsResponse *msg) { return UPB_FIELD_AT(msg, const struct google_protobuf_Duration*, UPB_SIZE(4, 8)); }
+UPB_INLINE bool envoy_service_load_stats_v2_LoadStatsResponse_report_endpoint_granularity(const envoy_service_load_stats_v2_LoadStatsResponse *msg) { return UPB_FIELD_AT(msg, bool, UPB_SIZE(0, 0)); }
+
+UPB_INLINE upb_strview* envoy_service_load_stats_v2_LoadStatsResponse_mutable_clusters(envoy_service_load_stats_v2_LoadStatsResponse *msg, size_t *len) {
+  return (upb_strview*)_upb_array_mutable_accessor(msg, UPB_SIZE(8, 16), len);
+}
+UPB_INLINE upb_strview* envoy_service_load_stats_v2_LoadStatsResponse_resize_clusters(envoy_service_load_stats_v2_LoadStatsResponse *msg, size_t len, upb_arena *arena) {
+  return (upb_strview*)_upb_array_resize_accessor(msg, UPB_SIZE(8, 16), len, UPB_SIZE(8, 16), UPB_TYPE_STRING, arena);
+}
+UPB_INLINE bool envoy_service_load_stats_v2_LoadStatsResponse_add_clusters(envoy_service_load_stats_v2_LoadStatsResponse *msg, upb_strview val, upb_arena *arena) {
+  return _upb_array_append_accessor(
+      msg, UPB_SIZE(8, 16), UPB_SIZE(8, 16), UPB_TYPE_STRING, &val, arena);
+}
+UPB_INLINE void envoy_service_load_stats_v2_LoadStatsResponse_set_load_reporting_interval(envoy_service_load_stats_v2_LoadStatsResponse *msg, struct google_protobuf_Duration* value) {
+  UPB_FIELD_AT(msg, struct google_protobuf_Duration*, UPB_SIZE(4, 8)) = value;
+}
+UPB_INLINE struct google_protobuf_Duration* envoy_service_load_stats_v2_LoadStatsResponse_mutable_load_reporting_interval(envoy_service_load_stats_v2_LoadStatsResponse *msg, upb_arena *arena) {
+  struct google_protobuf_Duration* sub = (struct google_protobuf_Duration*)envoy_service_load_stats_v2_LoadStatsResponse_load_reporting_interval(msg);
+  if (sub == NULL) {
+    sub = (struct google_protobuf_Duration*)upb_msg_new(&google_protobuf_Duration_msginit, arena);
+    if (!sub) return NULL;
+    envoy_service_load_stats_v2_LoadStatsResponse_set_load_reporting_interval(msg, sub);
+  }
+  return sub;
+}
+UPB_INLINE void envoy_service_load_stats_v2_LoadStatsResponse_set_report_endpoint_granularity(envoy_service_load_stats_v2_LoadStatsResponse *msg, bool value) {
+  UPB_FIELD_AT(msg, bool, UPB_SIZE(0, 0)) = value;
+}
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#include "upb/port_undef.inc"
+
+#endif  /* ENVOY_SERVICE_LOAD_STATS_V2_LRS_PROTO_UPB_H_ */
diff --git a/src/core/lib/channel/channel_args.cc b/src/core/lib/channel/channel_args.cc
index 2d9a1bc..a35db18 100644
--- a/src/core/lib/channel/channel_args.cc
+++ b/src/core/lib/channel/channel_args.cc
@@ -21,7 +21,6 @@
 #include <limits.h>
 #include <string.h>
 
-#include <grpc/compression.h>
 #include <grpc/grpc.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
@@ -213,106 +212,6 @@
   gpr_free(a);
 }
 
-grpc_compression_algorithm grpc_channel_args_get_compression_algorithm(
-    const grpc_channel_args* a) {
-  size_t i;
-  if (a == nullptr) return GRPC_COMPRESS_NONE;
-  for (i = 0; i < a->num_args; ++i) {
-    if (a->args[i].type == GRPC_ARG_INTEGER &&
-        !strcmp(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM, a->args[i].key)) {
-      return static_cast<grpc_compression_algorithm>(a->args[i].value.integer);
-      break;
-    }
-  }
-  return GRPC_COMPRESS_NONE;
-}
-
-grpc_channel_args* grpc_channel_args_set_compression_algorithm(
-    grpc_channel_args* a, grpc_compression_algorithm algorithm) {
-  GPR_ASSERT(algorithm < GRPC_COMPRESS_ALGORITHMS_COUNT);
-  grpc_arg tmp;
-  tmp.type = GRPC_ARG_INTEGER;
-  tmp.key = (char*)GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM;
-  tmp.value.integer = algorithm;
-  return grpc_channel_args_copy_and_add(a, &tmp, 1);
-}
-
-/** Returns 1 if the argument for compression algorithm's enabled states bitset
- * was found in \a a, returning the arg's value in \a states. Otherwise, returns
- * 0. */
-static int find_compression_algorithm_states_bitset(const grpc_channel_args* a,
-                                                    int** states_arg) {
-  if (a != nullptr) {
-    size_t i;
-    for (i = 0; i < a->num_args; ++i) {
-      if (a->args[i].type == GRPC_ARG_INTEGER &&
-          !strcmp(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET,
-                  a->args[i].key)) {
-        *states_arg = &a->args[i].value.integer;
-        **states_arg |= 0x1; /* forcefully enable support for no compression */
-        return 1;
-      }
-    }
-  }
-  return 0; /* GPR_FALSE */
-}
-
-grpc_channel_args* grpc_channel_args_compression_algorithm_set_state(
-    grpc_channel_args** a, grpc_compression_algorithm algorithm, int state) {
-  int* states_arg = nullptr;
-  grpc_channel_args* result = *a;
-  const int states_arg_found =
-      find_compression_algorithm_states_bitset(*a, &states_arg);
-
-  if (grpc_channel_args_get_compression_algorithm(*a) == algorithm &&
-      state == 0) {
-    const char* algo_name = nullptr;
-    GPR_ASSERT(grpc_compression_algorithm_name(algorithm, &algo_name) != 0);
-    gpr_log(GPR_ERROR,
-            "Tried to disable default compression algorithm '%s'. The "
-            "operation has been ignored.",
-            algo_name);
-  } else if (states_arg_found) {
-    if (state != 0) {
-      GPR_BITSET((unsigned*)states_arg, algorithm);
-    } else if (algorithm != GRPC_COMPRESS_NONE) {
-      GPR_BITCLEAR((unsigned*)states_arg, algorithm);
-    }
-  } else {
-    /* create a new arg */
-    grpc_arg tmp;
-    tmp.type = GRPC_ARG_INTEGER;
-    tmp.key = (char*)GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET;
-    /* all enabled by default */
-    tmp.value.integer = (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1;
-    if (state != 0) {
-      GPR_BITSET((unsigned*)&tmp.value.integer, algorithm);
-    } else if (algorithm != GRPC_COMPRESS_NONE) {
-      GPR_BITCLEAR((unsigned*)&tmp.value.integer, algorithm);
-    }
-    result = grpc_channel_args_copy_and_add(*a, &tmp, 1);
-    grpc_channel_args_destroy(*a);
-    *a = result;
-  }
-  return result;
-}
-
-uint32_t grpc_channel_args_compression_algorithm_get_states(
-    const grpc_channel_args* a) {
-  int* states_arg;
-  if (find_compression_algorithm_states_bitset(a, &states_arg)) {
-    return static_cast<uint32_t>(*states_arg);
-  } else {
-    return (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1; /* All algs. enabled */
-  }
-}
-
-grpc_channel_args* grpc_channel_args_set_socket_mutator(
-    grpc_channel_args* a, grpc_socket_mutator* mutator) {
-  grpc_arg tmp = grpc_socket_mutator_to_arg(mutator);
-  return grpc_channel_args_copy_and_add(a, &tmp, 1);
-}
-
 int grpc_channel_args_compare(const grpc_channel_args* a,
                               const grpc_channel_args* b) {
   int c = GPR_ICMP(a->num_args, b->num_args);
diff --git a/src/core/lib/channel/channel_args.h b/src/core/lib/channel/channel_args.h
index c47c027..2b698a6 100644
--- a/src/core/lib/channel/channel_args.h
+++ b/src/core/lib/channel/channel_args.h
@@ -21,9 +21,7 @@
 
 #include <grpc/support/port_platform.h>
 
-#include <grpc/compression.h>
 #include <grpc/grpc.h>
-#include "src/core/lib/iomgr/socket_mutator.h"
 
 // Channel args are intentionally immutable, to avoid the need for locking.
 
@@ -60,44 +58,9 @@
   grpc_channel_args_destroy(const_cast<grpc_channel_args*>(a));
 }
 
-/** Returns the compression algorithm set in \a a. */
-grpc_compression_algorithm grpc_channel_args_get_compression_algorithm(
-    const grpc_channel_args* a);
-
-/** Returns a channel arg instance with compression enabled. If \a a is
- * non-NULL, its args are copied. N.B. GRPC_COMPRESS_NONE disables compression
- * for the channel. */
-grpc_channel_args* grpc_channel_args_set_compression_algorithm(
-    grpc_channel_args* a, grpc_compression_algorithm algorithm);
-
-/** Sets the support for the given compression algorithm. By default, all
- * compression algorithms are enabled. It's an error to disable an algorithm set
- * by grpc_channel_args_set_compression_algorithm.
- *
- * Returns an instance with the updated algorithm states. The \a a pointer is
- * modified to point to the returned instance (which may be different from the
- * input value of \a a). */
-grpc_channel_args* grpc_channel_args_compression_algorithm_set_state(
-    grpc_channel_args** a, grpc_compression_algorithm algorithm, int enabled);
-
-/** Returns the bitset representing the support state (true for enabled, false
- * for disabled) for compression algorithms.
- *
- * The i-th bit of the returned bitset corresponds to the i-th entry in the
- * grpc_compression_algorithm enum. */
-uint32_t grpc_channel_args_compression_algorithm_get_states(
-    const grpc_channel_args* a);
-
 int grpc_channel_args_compare(const grpc_channel_args* a,
                               const grpc_channel_args* b);
 
-/** Returns a channel arg instance with socket mutator added. The socket mutator
- * will perform its mutate_fd method on all file descriptors used by the
- * channel.
- * If \a a is non-MULL, its args are copied. */
-grpc_channel_args* grpc_channel_args_set_socket_mutator(
-    grpc_channel_args* a, grpc_socket_mutator* mutator);
-
 /** Returns the value of argument \a name from \a args, or NULL if not found. */
 const grpc_arg* grpc_channel_args_find(const grpc_channel_args* args,
                                        const char* name);
diff --git a/src/core/lib/channel/channel_stack.h b/src/core/lib/channel/channel_stack.h
index 580e1e5..a7c28d0 100644
--- a/src/core/lib/channel/channel_stack.h
+++ b/src/core/lib/channel/channel_stack.h
@@ -42,7 +42,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/debug/trace.h"
-#include "src/core/lib/gpr/arena.h"
+#include "src/core/lib/gprpp/arena.h"
 #include "src/core/lib/iomgr/call_combiner.h"
 #include "src/core/lib/iomgr/polling_entity.h"
 #include "src/core/lib/transport/transport.h"
@@ -69,8 +69,8 @@
   const grpc_slice& path;
   gpr_timespec start_time;
   grpc_millis deadline;
-  gpr_arena* arena;
-  grpc_call_combiner* call_combiner;
+  grpc_core::Arena* arena;
+  grpc_core::CallCombiner* call_combiner;
 } grpc_call_element_args;
 
 typedef struct {
@@ -274,7 +274,11 @@
 
 extern grpc_core::TraceFlag grpc_trace_channel;
 
-#define GRPC_CALL_LOG_OP(sev, elem, op) \
-  if (grpc_trace_channel.enabled()) grpc_call_log_op(sev, elem, op)
+#define GRPC_CALL_LOG_OP(sev, elem, op)                \
+  do {                                                 \
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_channel)) { \
+      grpc_call_log_op(sev, elem, op);                 \
+    }                                                  \
+  } while (0)
 
 #endif /* GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_H */
diff --git a/src/core/lib/channel/channelz_registry.cc b/src/core/lib/channel/channelz_registry.cc
index 9f0169a..b6a660b 100644
--- a/src/core/lib/channel/channelz_registry.cc
+++ b/src/core/lib/channel/channelz_registry.cc
@@ -23,7 +23,7 @@
 #include "src/core/lib/channel/channelz_registry.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/memory.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
diff --git a/src/core/lib/channel/connected_channel.cc b/src/core/lib/channel/connected_channel.cc
index e2ea334..bd30c36 100644
--- a/src/core/lib/channel/connected_channel.cc
+++ b/src/core/lib/channel/connected_channel.cc
@@ -41,12 +41,12 @@
 typedef struct {
   grpc_closure closure;
   grpc_closure* original_closure;
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   const char* reason;
 } callback_state;
 
 typedef struct connected_channel_call_data {
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   // Closures used for returning results on the call combiner.
   callback_state on_complete[6];  // Max number of pending batches.
   callback_state recv_initial_metadata_ready;
diff --git a/src/core/lib/channel/context.h b/src/core/lib/channel/context.h
index 81b84f1..fd9d0ce 100644
--- a/src/core/lib/channel/context.h
+++ b/src/core/lib/channel/context.h
@@ -35,6 +35,9 @@
   /// Reserved for traffic_class_context.
   GRPC_CONTEXT_TRAFFIC,
 
+  /// Holds a pointer to ServiceConfig::CallData associated with this call.
+  GRPC_SERVICE_CONFIG_CALL_DATA,
+
   GRPC_CONTEXT_COUNT
 } grpc_context_index;
 
diff --git a/src/core/lib/channel/handshaker.cc b/src/core/lib/channel/handshaker.cc
index 6bb05ce..27ff250 100644
--- a/src/core/lib/channel/handshaker.cc
+++ b/src/core/lib/channel/handshaker.cc
@@ -94,7 +94,7 @@
 }
 
 void HandshakeManager::Add(RefCountedPtr<Handshaker> handshaker) {
-  if (grpc_handshaker_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_handshaker_trace)) {
     gpr_log(
         GPR_INFO,
         "handshake_manager %p: adding handshaker %s [%p] at index %" PRIuPTR,
@@ -125,7 +125,7 @@
 // on_handshake_done callback.
 // Returns true if we've scheduled the on_handshake_done callback.
 bool HandshakeManager::CallNextHandshakerLocked(grpc_error* error) {
-  if (grpc_handshaker_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_handshaker_trace)) {
     char* args_str = HandshakerArgsString(&args_);
     gpr_log(GPR_INFO,
             "handshake_manager %p: error=%s shutdown=%d index=%" PRIuPTR
@@ -159,7 +159,7 @@
         args_.read_buffer = nullptr;
       }
     }
-    if (grpc_handshaker_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_handshaker_trace)) {
       gpr_log(GPR_INFO,
               "handshake_manager %p: handshaking complete -- scheduling "
               "on_handshake_done with error=%s",
@@ -172,7 +172,7 @@
     is_shutdown_ = true;
   } else {
     auto handshaker = handshakers_[index_];
-    if (grpc_handshaker_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_handshaker_trace)) {
       gpr_log(
           GPR_INFO,
           "handshake_manager %p: calling handshaker %s [%p] at index %" PRIuPTR,
diff --git a/src/core/lib/channel/handshaker.h b/src/core/lib/channel/handshaker.h
index 912d524..b68799b 100644
--- a/src/core/lib/channel/handshaker.h
+++ b/src/core/lib/channel/handshaker.h
@@ -27,8 +27,8 @@
 
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
 #include "src/core/lib/gprpp/ref_counted.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/closure.h"
 #include "src/core/lib/iomgr/endpoint.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
diff --git a/src/core/lib/compression/compression_args.cc b/src/core/lib/compression/compression_args.cc
new file mode 100644
index 0000000..6a8232d
--- /dev/null
+++ b/src/core/lib/compression/compression_args.cc
@@ -0,0 +1,127 @@
+/*
+ *
+ * Copyright 2015 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <grpc/support/port_platform.h>
+
+#include <limits.h>
+#include <string.h>
+
+#include <grpc/compression.h>
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/compression/compression_args.h"
+#include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gpr/useful.h"
+
+grpc_compression_algorithm grpc_channel_args_get_compression_algorithm(
+    const grpc_channel_args* a) {
+  size_t i;
+  if (a == nullptr) return GRPC_COMPRESS_NONE;
+  for (i = 0; i < a->num_args; ++i) {
+    if (a->args[i].type == GRPC_ARG_INTEGER &&
+        !strcmp(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM, a->args[i].key)) {
+      return static_cast<grpc_compression_algorithm>(a->args[i].value.integer);
+      break;
+    }
+  }
+  return GRPC_COMPRESS_NONE;
+}
+
+grpc_channel_args* grpc_channel_args_set_compression_algorithm(
+    grpc_channel_args* a, grpc_compression_algorithm algorithm) {
+  GPR_ASSERT(algorithm < GRPC_COMPRESS_ALGORITHMS_COUNT);
+  grpc_arg tmp;
+  tmp.type = GRPC_ARG_INTEGER;
+  tmp.key = (char*)GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM;
+  tmp.value.integer = algorithm;
+  return grpc_channel_args_copy_and_add(a, &tmp, 1);
+}
+
+/** Returns 1 if the argument for compression algorithm's enabled states bitset
+ * was found in \a a, returning the arg's value in \a states. Otherwise, returns
+ * 0. */
+static int find_compression_algorithm_states_bitset(const grpc_channel_args* a,
+                                                    int** states_arg) {
+  if (a != nullptr) {
+    size_t i;
+    for (i = 0; i < a->num_args; ++i) {
+      if (a->args[i].type == GRPC_ARG_INTEGER &&
+          !strcmp(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET,
+                  a->args[i].key)) {
+        *states_arg = &a->args[i].value.integer;
+        **states_arg |= 0x1; /* forcefully enable support for no compression */
+        return 1;
+      }
+    }
+  }
+  return 0; /* GPR_FALSE */
+}
+
+grpc_channel_args* grpc_channel_args_compression_algorithm_set_state(
+    grpc_channel_args** a, grpc_compression_algorithm algorithm, int state) {
+  int* states_arg = nullptr;
+  grpc_channel_args* result = *a;
+  const int states_arg_found =
+      find_compression_algorithm_states_bitset(*a, &states_arg);
+
+  if (grpc_channel_args_get_compression_algorithm(*a) == algorithm &&
+      state == 0) {
+    const char* algo_name = nullptr;
+    GPR_ASSERT(grpc_compression_algorithm_name(algorithm, &algo_name) != 0);
+    gpr_log(GPR_ERROR,
+            "Tried to disable default compression algorithm '%s'. The "
+            "operation has been ignored.",
+            algo_name);
+  } else if (states_arg_found) {
+    if (state != 0) {
+      GPR_BITSET((unsigned*)states_arg, algorithm);
+    } else if (algorithm != GRPC_COMPRESS_NONE) {
+      GPR_BITCLEAR((unsigned*)states_arg, algorithm);
+    }
+  } else {
+    /* create a new arg */
+    grpc_arg tmp;
+    tmp.type = GRPC_ARG_INTEGER;
+    tmp.key = (char*)GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET;
+    /* all enabled by default */
+    tmp.value.integer = (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1;
+    if (state != 0) {
+      GPR_BITSET((unsigned*)&tmp.value.integer, algorithm);
+    } else if (algorithm != GRPC_COMPRESS_NONE) {
+      GPR_BITCLEAR((unsigned*)&tmp.value.integer, algorithm);
+    }
+    result = grpc_channel_args_copy_and_add(*a, &tmp, 1);
+    grpc_channel_args_destroy(*a);
+    *a = result;
+  }
+  return result;
+}
+
+uint32_t grpc_channel_args_compression_algorithm_get_states(
+    const grpc_channel_args* a) {
+  int* states_arg;
+  if (find_compression_algorithm_states_bitset(a, &states_arg)) {
+    return static_cast<uint32_t>(*states_arg);
+  } else {
+    return (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1; /* All algs. enabled */
+  }
+}
diff --git a/src/core/lib/compression/compression_args.h b/src/core/lib/compression/compression_args.h
new file mode 100644
index 0000000..407d6e2
--- /dev/null
+++ b/src/core/lib/compression/compression_args.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * 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_CORE_LIB_COMPRESSION_COMPRESSION_ARGS_H
+#define GRPC_CORE_LIB_COMPRESSION_COMPRESSION_ARGS_H
+
+#include <grpc/support/port_platform.h>
+
+#include <grpc/compression.h>
+#include <grpc/impl/codegen/grpc_types.h>
+
+/** Returns the compression algorithm set in \a a. */
+grpc_compression_algorithm grpc_channel_args_get_compression_algorithm(
+    const grpc_channel_args* a);
+
+/** Returns a channel arg instance with compression enabled. If \a a is
+ * non-NULL, its args are copied. N.B. GRPC_COMPRESS_NONE disables compression
+ * for the channel. */
+grpc_channel_args* grpc_channel_args_set_compression_algorithm(
+    grpc_channel_args* a, grpc_compression_algorithm algorithm);
+
+/** Sets the support for the given compression algorithm. By default, all
+ * compression algorithms are enabled. It's an error to disable an algorithm set
+ * by grpc_channel_args_set_compression_algorithm.
+ *
+ * Returns an instance with the updated algorithm states. The \a a pointer is
+ * modified to point to the returned instance (which may be different from the
+ * input value of \a a). */
+grpc_channel_args* grpc_channel_args_compression_algorithm_set_state(
+    grpc_channel_args** a, grpc_compression_algorithm algorithm, int state);
+
+/** Returns the bitset representing the support state (true for enabled, false
+ * for disabled) for compression algorithms.
+ *
+ * The i-th bit of the returned bitset corresponds to the i-th entry in the
+ * grpc_compression_algorithm enum. */
+uint32_t grpc_channel_args_compression_algorithm_get_states(
+    const grpc_channel_args* a);
+
+#endif /* GRPC_CORE_LIB_COMPRESSION_COMPRESSION_ARGS_H */
diff --git a/src/core/lib/debug/trace.cc b/src/core/lib/debug/trace.cc
index cafdb15..84c0a38 100644
--- a/src/core/lib/debug/trace.cc
+++ b/src/core/lib/debug/trace.cc
@@ -26,7 +26,11 @@
 #include <grpc/grpc.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
-#include "src/core/lib/gpr/env.h"
+
+GPR_GLOBAL_CONFIG_DEFINE_STRING(
+    grpc_trace, "",
+    "A comma separated list of tracers that provide additional insight into "
+    "how gRPC C core is processing requests via debug logs.");
 
 int grpc_tracer_set_enabled(const char* name, int enabled);
 
@@ -133,12 +137,14 @@
   gpr_free(strings);
 }
 
-void grpc_tracer_init(const char* env_var) {
-  char* e = gpr_getenv(env_var);
-  if (e != nullptr) {
-    parse(e);
-    gpr_free(e);
-  }
+void grpc_tracer_init(const char* env_var_name) {
+  (void)env_var_name;  // suppress unused variable error
+  grpc_tracer_init();
+}
+
+void grpc_tracer_init() {
+  grpc_core::UniquePtr<char> value = GPR_GLOBAL_CONFIG_GET(grpc_trace);
+  parse(value.get());
 }
 
 void grpc_tracer_shutdown(void) {}
diff --git a/src/core/lib/debug/trace.h b/src/core/lib/debug/trace.h
index 6108fb2..6a4a803 100644
--- a/src/core/lib/debug/trace.h
+++ b/src/core/lib/debug/trace.h
@@ -24,7 +24,15 @@
 #include <grpc/support/atm.h>
 #include <stdbool.h>
 
+#include "src/core/lib/gprpp/global_config.h"
+
+GPR_GLOBAL_CONFIG_DECLARE_STRING(grpc_trace);
+
+// TODO(veblush): Remove this deprecated function once codes depending on this
+// function are updated in the internal repo.
 void grpc_tracer_init(const char* env_var_name);
+
+void grpc_tracer_init();
 void grpc_tracer_shutdown(void);
 
 #if defined(__has_feature)
@@ -65,6 +73,8 @@
 // wrapped language (wr don't want to force recompilation to get tracing).
 // Internally, however, for performance reasons, we compile them out by
 // default, since internal build systems make recompiling trivial.
+//
+// Prefer GRPC_TRACE_FLAG_ENABLED() macro instead of using enabled() directly.
 #define GRPC_USE_TRACERS  // tracers on by default in OSS
 #if defined(GRPC_USE_TRACERS) || !defined(NDEBUG)
   bool enabled() {
@@ -99,6 +109,8 @@
 #endif
 };
 
+#define GRPC_TRACE_FLAG_ENABLED(f) GPR_UNLIKELY((f).enabled())
+
 #ifndef NDEBUG
 typedef TraceFlag DebugOnlyTraceFlag;
 #else
diff --git a/src/core/lib/gpr/arena.cc b/src/core/lib/gpr/arena.cc
deleted file mode 100644
index 836a7ca..0000000
--- a/src/core/lib/gpr/arena.cc
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- *
- * Copyright 2017 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 <grpc/support/port_platform.h>
-
-#include "src/core/lib/gpr/arena.h"
-
-#include <string.h>
-#include <new>
-
-#include <grpc/support/alloc.h>
-#include <grpc/support/atm.h>
-#include <grpc/support/log.h>
-#include <grpc/support/sync.h>
-
-#include "src/core/lib/gpr/alloc.h"
-#include "src/core/lib/gpr/env.h"
-#include "src/core/lib/gprpp/memory.h"
-
-namespace {
-enum init_strategy {
-  NO_INIT,        // Do not initialize the arena blocks.
-  ZERO_INIT,      // Initialize arena blocks with 0.
-  NON_ZERO_INIT,  // Initialize arena blocks with a non-zero value.
-};
-
-gpr_once g_init_strategy_once = GPR_ONCE_INIT;
-init_strategy g_init_strategy = NO_INIT;
-}  // namespace
-
-static void set_strategy_from_env() {
-  char* str = gpr_getenv("GRPC_ARENA_INIT_STRATEGY");
-  if (str == nullptr) {
-    g_init_strategy = NO_INIT;
-  } else if (strcmp(str, "zero_init") == 0) {
-    g_init_strategy = ZERO_INIT;
-  } else if (strcmp(str, "non_zero_init") == 0) {
-    g_init_strategy = NON_ZERO_INIT;
-  } else {
-    g_init_strategy = NO_INIT;
-  }
-  gpr_free(str);
-}
-
-static void* gpr_arena_alloc_maybe_init(size_t size) {
-  void* mem = gpr_malloc_aligned(size, GPR_MAX_ALIGNMENT);
-  gpr_once_init(&g_init_strategy_once, set_strategy_from_env);
-  if (GPR_UNLIKELY(g_init_strategy != NO_INIT)) {
-    if (g_init_strategy == ZERO_INIT) {
-      memset(mem, 0, size);
-    } else {  // NON_ZERO_INIT.
-      memset(mem, 0xFE, size);
-    }
-  }
-  return mem;
-}
-
-void gpr_arena_init() {
-  gpr_once_init(&g_init_strategy_once, set_strategy_from_env);
-}
-
-// Uncomment this to use a simple arena that simply allocates the
-// requested amount of memory for each call to gpr_arena_alloc().  This
-// effectively eliminates the efficiency gain of using an arena, but it
-// may be useful for debugging purposes.
-//#define SIMPLE_ARENA_FOR_DEBUGGING
-#ifdef SIMPLE_ARENA_FOR_DEBUGGING
-
-struct gpr_arena {
-  gpr_arena() { gpr_mu_init(&mu); }
-  ~gpr_arena() {
-    gpr_mu_destroy(&mu);
-    for (size_t i = 0; i < num_ptrs; ++i) {
-      gpr_free_aligned(ptrs[i]);
-    }
-    gpr_free(ptrs);
-  }
-
-  gpr_mu mu;
-  void** ptrs = nullptr;
-  size_t num_ptrs = 0;
-};
-
-gpr_arena* gpr_arena_create(size_t ignored_initial_size) {
-  return grpc_core::New<gpr_arena>();
-}
-
-size_t gpr_arena_destroy(gpr_arena* arena) {
-  grpc_core::Delete(arena);
-  return 1;  // Value doesn't matter, since it won't be used.
-}
-
-void* gpr_arena_alloc(gpr_arena* arena, size_t size) {
-  gpr_mu_lock(&arena->mu);
-  arena->ptrs =
-      (void**)gpr_realloc(arena->ptrs, sizeof(void*) * (arena->num_ptrs + 1));
-  void* retval = arena->ptrs[arena->num_ptrs++] =
-      gpr_arena_alloc_maybe_init(size);
-  gpr_mu_unlock(&arena->mu);
-  return retval;
-}
-
-#else  // SIMPLE_ARENA_FOR_DEBUGGING
-
-// TODO(roth): We currently assume that all callers need alignment of 16
-// bytes, which may be wrong in some cases.  As part of converting the
-// arena API to C++, we should consider replacing gpr_arena_alloc() with a
-// template that takes the type of the value being allocated, which
-// would allow us to use the alignment actually needed by the caller.
-
-typedef struct zone {
-  zone* next = nullptr;
-} zone;
-
-struct gpr_arena {
-  gpr_arena(size_t initial_size)
-      : initial_zone_size(initial_size), last_zone(&initial_zone) {
-    gpr_mu_init(&arena_growth_mutex);
-  }
-  ~gpr_arena() {
-    gpr_mu_destroy(&arena_growth_mutex);
-    zone* z = initial_zone.next;
-    while (z) {
-      zone* next_z = z->next;
-      z->~zone();
-      gpr_free_aligned(z);
-      z = next_z;
-    }
-  }
-
-  // Keep track of the total used size. We use this in our call sizing
-  // historesis.
-  gpr_atm total_used = 0;
-  size_t initial_zone_size;
-  zone initial_zone;
-  zone* last_zone;
-  gpr_mu arena_growth_mutex;
-};
-
-gpr_arena* gpr_arena_create(size_t initial_size) {
-  initial_size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(initial_size);
-  return new (gpr_arena_alloc_maybe_init(
-      GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + initial_size))
-      gpr_arena(initial_size);
-}
-
-size_t gpr_arena_destroy(gpr_arena* arena) {
-  const gpr_atm size = gpr_atm_no_barrier_load(&arena->total_used);
-  arena->~gpr_arena();
-  gpr_free_aligned(arena);
-  return static_cast<size_t>(size);
-}
-
-void* gpr_arena_alloc(gpr_arena* arena, size_t size) {
-  size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(size);
-  size_t begin = gpr_atm_no_barrier_fetch_add(&arena->total_used, size);
-  if (begin + size <= arena->initial_zone_size) {
-    return reinterpret_cast<char*>(arena) +
-           GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + begin;
-  } else {
-    // If the allocation isn't able to end in the initial zone, create a new
-    // zone for this allocation, and any unused space in the initial zone is
-    // wasted. This overflowing and wasting is uncommon because of our arena
-    // sizing historesis (that is, most calls should have a large enough initial
-    // zone and will not need to grow the arena).
-    gpr_mu_lock(&arena->arena_growth_mutex);
-    zone* z = new (gpr_arena_alloc_maybe_init(
-        GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone)) + size)) zone();
-    arena->last_zone->next = z;
-    arena->last_zone = z;
-    gpr_mu_unlock(&arena->arena_growth_mutex);
-    return reinterpret_cast<char*>(z) +
-           GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone));
-  }
-}
-
-#endif  // SIMPLE_ARENA_FOR_DEBUGGING
diff --git a/src/core/lib/gpr/arena.h b/src/core/lib/gpr/arena.h
index 069892b..4d70695 100644
--- a/src/core/lib/gpr/arena.h
+++ b/src/core/lib/gpr/arena.h
@@ -21,23 +21,27 @@
 // the arena as a whole is freed
 // Tracks the total memory allocated against it, so that future arenas can
 // pre-allocate the right amount of memory
+// This transitional API is deprecated and will be removed soon in favour of
+// src/core/lib/gprpp/arena.h .
 
 #ifndef GRPC_CORE_LIB_GPR_ARENA_H
 #define GRPC_CORE_LIB_GPR_ARENA_H
 
 #include <grpc/support/port_platform.h>
 
-#include <stddef.h>
+#include "src/core/lib/gprpp/arena.h"
 
-typedef struct gpr_arena gpr_arena;
-
+// TODO(arjunroy) : Remove deprecated gpr_arena API once all callers are gone.
+typedef class grpc_core::Arena gpr_arena;
 // Create an arena, with \a initial_size bytes in the first allocated buffer
-gpr_arena* gpr_arena_create(size_t initial_size);
-// Allocate \a size bytes from the arena
-void* gpr_arena_alloc(gpr_arena* arena, size_t size);
+inline gpr_arena* gpr_arena_create(size_t initial_size) {
+  return grpc_core::Arena::Create(initial_size);
+}
 // Destroy an arena, returning the total number of bytes allocated
-size_t gpr_arena_destroy(gpr_arena* arena);
-// Initializes the Arena component.
-void gpr_arena_init();
+inline size_t gpr_arena_destroy(gpr_arena* arena) { return arena->Destroy(); }
+// Allocate \a size bytes from the arena
+inline void* gpr_arena_alloc(gpr_arena* arena, size_t size) {
+  return arena->Alloc(size);
+}
 
 #endif /* GRPC_CORE_LIB_GPR_ARENA_H */
diff --git a/src/core/lib/gpr/env.h b/src/core/lib/gpr/env.h
index aec8a31..11db17c 100644
--- a/src/core/lib/gpr/env.h
+++ b/src/core/lib/gpr/env.h
@@ -34,10 +34,7 @@
 /* Sets the environment with the specified name to the specified value. */
 void gpr_setenv(const char* name, const char* value);
 
-/* This is a version of gpr_getenv that does not produce any output if it has to
-   use an insecure version of the function. It is ONLY to be used to solve the
-   problem in which we need to check an env variable to configure the verbosity
-   level of logging. So DO NOT USE THIS. */
-const char* gpr_getenv_silent(const char* name, char** dst);
+/* Deletes the variable name from the environment. */
+void gpr_unsetenv(const char* name);
 
 #endif /* GRPC_CORE_LIB_GPR_ENV_H */
diff --git a/src/core/lib/gpr/env_linux.cc b/src/core/lib/gpr/env_linux.cc
index fadc42f..3a3aa54 100644
--- a/src/core/lib/gpr/env_linux.cc
+++ b/src/core/lib/gpr/env_linux.cc
@@ -38,7 +38,7 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/useful.h"
 
-const char* gpr_getenv_silent(const char* name, char** dst) {
+static const char* gpr_getenv_silent(const char* name, char** dst) {
   const char* insecure_func_used = nullptr;
   char* result = nullptr;
 #if defined(GPR_BACKWARDS_COMPATIBILITY_MODE)
@@ -79,4 +79,9 @@
   GPR_ASSERT(res == 0);
 }
 
+void gpr_unsetenv(const char* name) {
+  int res = unsetenv(name);
+  GPR_ASSERT(res == 0);
+}
+
 #endif /* GPR_LINUX_ENV */
diff --git a/src/core/lib/gpr/env_posix.cc b/src/core/lib/gpr/env_posix.cc
index 599f85a..30ddc50 100644
--- a/src/core/lib/gpr/env_posix.cc
+++ b/src/core/lib/gpr/env_posix.cc
@@ -44,4 +44,9 @@
   GPR_ASSERT(res == 0);
 }
 
+void gpr_unsetenv(const char* name) {
+  int res = unsetenv(name);
+  GPR_ASSERT(res == 0);
+}
+
 #endif /* GPR_POSIX_ENV */
diff --git a/src/core/lib/gpr/env_windows.cc b/src/core/lib/gpr/env_windows.cc
index cf8ed60..76c45fb 100644
--- a/src/core/lib/gpr/env_windows.cc
+++ b/src/core/lib/gpr/env_windows.cc
@@ -30,11 +30,6 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/string_windows.h"
 
-const char* gpr_getenv_silent(const char* name, char** dst) {
-  *dst = gpr_getenv(name);
-  return NULL;
-}
-
 char* gpr_getenv(const char* name) {
   char* result = NULL;
   DWORD size;
@@ -69,4 +64,11 @@
   GPR_ASSERT(res);
 }
 
+void gpr_unsetenv(const char* name) {
+  LPTSTR tname = gpr_char_to_tchar(name);
+  BOOL res = SetEnvironmentVariable(tname, NULL);
+  gpr_free(tname);
+  GPR_ASSERT(res);
+}
+
 #endif /* GPR_WINDOWS_ENV */
diff --git a/src/core/lib/gpr/log.cc b/src/core/lib/gpr/log.cc
index 01ef112..8a229b2 100644
--- a/src/core/lib/gpr/log.cc
+++ b/src/core/lib/gpr/log.cc
@@ -22,12 +22,15 @@
 #include <grpc/support/atm.h>
 #include <grpc/support/log.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gprpp/global_config.h"
 
 #include <stdio.h>
 #include <string.h>
 
+GPR_GLOBAL_CONFIG_DEFINE_STRING(grpc_verbosity, "ERROR",
+                                "Default gRPC logging verbosity")
+
 void gpr_default_log(gpr_log_func_args* args);
 static gpr_atm g_log_func = (gpr_atm)gpr_default_log;
 static gpr_atm g_min_severity_to_print = GPR_LOG_VERBOSITY_UNSET;
@@ -72,29 +75,22 @@
 }
 
 void gpr_log_verbosity_init() {
-  char* verbosity = nullptr;
-  const char* insecure_getenv = gpr_getenv_silent("GRPC_VERBOSITY", &verbosity);
+  grpc_core::UniquePtr<char> verbosity = GPR_GLOBAL_CONFIG_GET(grpc_verbosity);
 
   gpr_atm min_severity_to_print = GPR_LOG_SEVERITY_ERROR;
-  if (verbosity != nullptr) {
-    if (gpr_stricmp(verbosity, "DEBUG") == 0) {
+  if (strlen(verbosity.get()) > 0) {
+    if (gpr_stricmp(verbosity.get(), "DEBUG") == 0) {
       min_severity_to_print = static_cast<gpr_atm>(GPR_LOG_SEVERITY_DEBUG);
-    } else if (gpr_stricmp(verbosity, "INFO") == 0) {
+    } else if (gpr_stricmp(verbosity.get(), "INFO") == 0) {
       min_severity_to_print = static_cast<gpr_atm>(GPR_LOG_SEVERITY_INFO);
-    } else if (gpr_stricmp(verbosity, "ERROR") == 0) {
+    } else if (gpr_stricmp(verbosity.get(), "ERROR") == 0) {
       min_severity_to_print = static_cast<gpr_atm>(GPR_LOG_SEVERITY_ERROR);
     }
-    gpr_free(verbosity);
   }
   if ((gpr_atm_no_barrier_load(&g_min_severity_to_print)) ==
       GPR_LOG_VERBOSITY_UNSET) {
     gpr_atm_no_barrier_store(&g_min_severity_to_print, min_severity_to_print);
   }
-
-  if (insecure_getenv != nullptr) {
-    gpr_log(GPR_DEBUG, "Warning: insecure environment read function '%s' used",
-            insecure_getenv);
-  }
 }
 
 void gpr_set_log_function(gpr_log_func f) {
diff --git a/src/core/lib/gpr/string.cc b/src/core/lib/gpr/string.cc
index 0a76fc1..31d5fde 100644
--- a/src/core/lib/gpr/string.cc
+++ b/src/core/lib/gpr/string.cc
@@ -332,16 +332,22 @@
   return nullptr;
 }
 
-bool gpr_is_true(const char* s) {
-  size_t i;
+bool gpr_parse_bool_value(const char* s, bool* dst) {
+  const char* kTrue[] = {"1", "t", "true", "y", "yes"};
+  const char* kFalse[] = {"0", "f", "false", "n", "no"};
+  static_assert(sizeof(kTrue) == sizeof(kFalse), "true_false_equal");
+
   if (s == nullptr) {
     return false;
   }
-  static const char* truthy[] = {"yes", "true", "1"};
-  for (i = 0; i < GPR_ARRAY_SIZE(truthy); i++) {
-    if (0 == gpr_stricmp(s, truthy[i])) {
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(kTrue); ++i) {
+    if (gpr_stricmp(s, kTrue[i]) == 0) {
+      *dst = true;
+      return true;
+    } else if (gpr_stricmp(s, kFalse[i]) == 0) {
+      *dst = false;
       return true;
     }
   }
-  return false;
+  return false;  // didn't match a legal input
 }
diff --git a/src/core/lib/gpr/string.h b/src/core/lib/gpr/string.h
index ce51fe4..c5efcec 100644
--- a/src/core/lib/gpr/string.h
+++ b/src/core/lib/gpr/string.h
@@ -113,7 +113,9 @@
 
 void* gpr_memrchr(const void* s, int c, size_t n);
 
-/** Return true if lower(s) equals "true", "yes" or "1", otherwise false. */
-bool gpr_is_true(const char* s);
+/* Try to parse given string into a boolean value.
+   When parsed successfully, dst will have the value and returns true.
+   Otherwise, it returns false. */
+bool gpr_parse_bool_value(const char* value, bool* dst);
 
 #endif /* GRPC_CORE_LIB_GPR_STRING_H */
diff --git a/src/core/lib/gpr/time_posix.cc b/src/core/lib/gpr/time_posix.cc
index 1b3e364..b5a4767 100644
--- a/src/core/lib/gpr/time_posix.cc
+++ b/src/core/lib/gpr/time_posix.cc
@@ -108,6 +108,9 @@
   now.clock_type = clock;
   switch (clock) {
     case GPR_CLOCK_REALTIME:
+      // gettimeofday(...) function may return with a value whose tv_usec is
+      // greater than 1e6 on iOS The case is resolved with the guard at end of
+      // this function.
       gettimeofday(&now_tv, nullptr);
       now.tv_sec = now_tv.tv_sec;
       now.tv_nsec = now_tv.tv_usec * 1000;
@@ -124,6 +127,16 @@
       abort();
   }
 
+  // Guard the tv_nsec field in valid range for all clock types
+  while (GPR_UNLIKELY(now.tv_nsec >= 1e9)) {
+    now.tv_sec++;
+    now.tv_nsec -= 1e9;
+  }
+  while (GPR_UNLIKELY(now.tv_nsec < 0)) {
+    now.tv_sec--;
+    now.tv_nsec += 1e9;
+  }
+
   return now;
 }
 #endif
diff --git a/src/core/lib/gprpp/arena.cc b/src/core/lib/gprpp/arena.cc
new file mode 100644
index 0000000..5c344db
--- /dev/null
+++ b/src/core/lib/gprpp/arena.cc
@@ -0,0 +1,103 @@
+/*
+ *
+ * Copyright 2017 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 <grpc/support/port_platform.h>
+
+#include "src/core/lib/gprpp/arena.h"
+
+#include <string.h>
+#include <new>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/atm.h>
+#include <grpc/support/log.h>
+#include <grpc/support/sync.h>
+
+#include "src/core/lib/gpr/alloc.h"
+#include "src/core/lib/gprpp/memory.h"
+
+namespace {
+
+void* ArenaStorage(size_t initial_size) {
+  static constexpr size_t base_size =
+      GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_core::Arena));
+  initial_size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(initial_size);
+  size_t alloc_size = base_size + initial_size;
+  static constexpr size_t alignment =
+      (GPR_CACHELINE_SIZE > GPR_MAX_ALIGNMENT &&
+       GPR_CACHELINE_SIZE % GPR_MAX_ALIGNMENT == 0)
+          ? GPR_CACHELINE_SIZE
+          : GPR_MAX_ALIGNMENT;
+  return gpr_malloc_aligned(alloc_size, alignment);
+}
+
+}  // namespace
+
+namespace grpc_core {
+
+Arena::~Arena() {
+  Zone* z = last_zone_;
+  while (z) {
+    Zone* prev_z = z->prev;
+    z->~Zone();
+    gpr_free_aligned(z);
+    z = prev_z;
+  }
+}
+
+Arena* Arena::Create(size_t initial_size) {
+  return new (ArenaStorage(initial_size)) Arena(initial_size);
+}
+
+Pair<Arena*, void*> Arena::CreateWithAlloc(size_t initial_size,
+                                           size_t alloc_size) {
+  static constexpr size_t base_size =
+      GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(Arena));
+  auto* new_arena =
+      new (ArenaStorage(initial_size)) Arena(initial_size, alloc_size);
+  void* first_alloc = reinterpret_cast<char*>(new_arena) + base_size;
+  return MakePair(new_arena, first_alloc);
+}
+
+size_t Arena::Destroy() {
+  size_t size = total_used_.Load(MemoryOrder::RELAXED);
+  this->~Arena();
+  gpr_free_aligned(this);
+  return size;
+}
+
+void* Arena::AllocZone(size_t size) {
+  // If the allocation isn't able to end in the initial zone, create a new
+  // zone for this allocation, and any unused space in the initial zone is
+  // wasted. This overflowing and wasting is uncommon because of our arena
+  // sizing hysteresis (that is, most calls should have a large enough initial
+  // zone and will not need to grow the arena).
+  static constexpr size_t zone_base_size =
+      GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(Zone));
+  size_t alloc_size = zone_base_size + size;
+  Zone* z = new (gpr_malloc_aligned(alloc_size, GPR_MAX_ALIGNMENT)) Zone();
+  {
+    gpr_spinlock_lock(&arena_growth_spinlock_);
+    z->prev = last_zone_;
+    last_zone_ = z;
+    gpr_spinlock_unlock(&arena_growth_spinlock_);
+  }
+  return reinterpret_cast<char*>(z) + zone_base_size;
+}
+
+}  // namespace grpc_core
diff --git a/src/core/lib/gprpp/arena.h b/src/core/lib/gprpp/arena.h
new file mode 100644
index 0000000..b1b0c4a
--- /dev/null
+++ b/src/core/lib/gprpp/arena.h
@@ -0,0 +1,121 @@
+/*
+ *
+ * Copyright 2017 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.
+ *
+ */
+
+// \file Arena based allocator
+// Allows very fast allocation of memory, but that memory cannot be freed until
+// the arena as a whole is freed
+// Tracks the total memory allocated against it, so that future arenas can
+// pre-allocate the right amount of memory
+
+#ifndef GRPC_CORE_LIB_GPRPP_ARENA_H
+#define GRPC_CORE_LIB_GPRPP_ARENA_H
+
+#include <grpc/support/port_platform.h>
+
+#include <new>
+#include <utility>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/sync.h>
+
+#include "src/core/lib/gpr/alloc.h"
+#include "src/core/lib/gpr/spinlock.h"
+#include "src/core/lib/gprpp/atomic.h"
+#include "src/core/lib/gprpp/pair.h"
+
+#include <stddef.h>
+
+namespace grpc_core {
+
+class Arena {
+ public:
+  // Create an arena, with \a initial_size bytes in the first allocated buffer.
+  static Arena* Create(size_t initial_size);
+
+  // Create an arena, with \a initial_size bytes in the first allocated buffer,
+  // and return both a void pointer to the returned arena and a void* with the
+  // first allocation.
+  static Pair<Arena*, void*> CreateWithAlloc(size_t initial_size,
+                                             size_t alloc_size);
+
+  // Destroy an arena, returning the total number of bytes allocated.
+  size_t Destroy();
+  // Allocate \a size bytes from the arena.
+  void* Alloc(size_t size) {
+    static constexpr size_t base_size =
+        GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(Arena));
+    size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(size);
+    size_t begin = total_used_.FetchAdd(size, MemoryOrder::RELAXED);
+    if (begin + size <= initial_zone_size_) {
+      return reinterpret_cast<char*>(this) + base_size + begin;
+    } else {
+      return AllocZone(size);
+    }
+  }
+
+  // TODO(roth): We currently assume that all callers need alignment of 16
+  // bytes, which may be wrong in some cases. When we have time, we should
+  // change this to instead use the alignment of the type being allocated by
+  // this method.
+  template <typename T, typename... Args>
+  T* New(Args&&... args) {
+    T* t = static_cast<T*>(Alloc(sizeof(T)));
+    new (t) T(std::forward<Args>(args)...);
+    return t;
+  }
+
+ private:
+  struct Zone {
+    Zone* prev;
+  };
+
+  // Initialize an arena.
+  // Parameters:
+  //   initial_size: The initial size of the whole arena in bytes. These bytes
+  //   are contained within 'zone 0'. If the arena user ends up requiring more
+  //   memory than the arena contains in zone 0, subsequent zones are allocated
+  //   on demand and maintained in a tail-linked list.
+  //
+  //   initial_alloc: Optionally, construct the arena as though a call to
+  //   Alloc() had already been made for initial_alloc bytes. This provides a
+  //   quick optimization (avoiding an atomic fetch-add) for the common case
+  //   where we wish to create an arena and then perform an immediate
+  //   allocation.
+  explicit Arena(size_t initial_size, size_t initial_alloc = 0)
+      : total_used_(initial_alloc), initial_zone_size_(initial_size) {}
+
+  ~Arena();
+
+  void* AllocZone(size_t size);
+
+  // Keep track of the total used size. We use this in our call sizing
+  // hysteresis.
+  Atomic<size_t> total_used_;
+  size_t initial_zone_size_;
+  gpr_spinlock arena_growth_spinlock_ = GPR_SPINLOCK_STATIC_INITIALIZER;
+  // If the initial arena allocation wasn't enough, we allocate additional zones
+  // in a reverse linked list. Each additional zone consists of (1) a pointer to
+  // the zone added before this zone (null if this is the first additional zone)
+  // and (2) the allocated memory. The arena itself maintains a pointer to the
+  // last zone; the zone list is reverse-walked during arena destruction only.
+  Zone* last_zone_ = nullptr;
+};
+
+}  // namespace grpc_core
+
+#endif /* GRPC_CORE_LIB_GPRPP_ARENA_H */
diff --git a/src/core/lib/gprpp/fork.cc b/src/core/lib/gprpp/fork.cc
index c4b1cbc..cacf5e8 100644
--- a/src/core/lib/gprpp/fork.cc
+++ b/src/core/lib/gprpp/fork.cc
@@ -26,8 +26,8 @@
 #include <grpc/support/sync.h>
 #include <grpc/support/time.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/gprpp/memory.h"
 
 /*
@@ -35,6 +35,16 @@
  *       AROUND VERY SPECIFIC USE CASES.
  */
 
+#ifdef GRPC_ENABLE_FORK_SUPPORT
+#define GRPC_ENABLE_FORK_SUPPORT_DEFAULT true
+#else
+#define GRPC_ENABLE_FORK_SUPPORT_DEFAULT false
+#endif  // GRPC_ENABLE_FORK_SUPPORT
+
+GPR_GLOBAL_CONFIG_DEFINE_BOOL(grpc_enable_fork_support,
+                              GRPC_ENABLE_FORK_SUPPORT_DEFAULT,
+                              "Enable folk support");
+
 namespace grpc_core {
 namespace internal {
 // The exec_ctx_count has 2 modes, blocked and unblocked.
@@ -158,34 +168,7 @@
 
 void Fork::GlobalInit() {
   if (!override_enabled_) {
-#ifdef GRPC_ENABLE_FORK_SUPPORT
-    support_enabled_ = true;
-#endif
-    bool env_var_set = false;
-    char* env = gpr_getenv("GRPC_ENABLE_FORK_SUPPORT");
-    if (env != nullptr) {
-      static const char* truthy[] = {"yes",  "Yes",  "YES", "true",
-                                     "True", "TRUE", "1"};
-      static const char* falsey[] = {"no",    "No",    "NO", "false",
-                                     "False", "FALSE", "0"};
-      for (size_t i = 0; i < GPR_ARRAY_SIZE(truthy); i++) {
-        if (0 == strcmp(env, truthy[i])) {
-          support_enabled_ = true;
-          env_var_set = true;
-          break;
-        }
-      }
-      if (!env_var_set) {
-        for (size_t i = 0; i < GPR_ARRAY_SIZE(falsey); i++) {
-          if (0 == strcmp(env, falsey[i])) {
-            support_enabled_ = false;
-            env_var_set = true;
-            break;
-          }
-        }
-      }
-      gpr_free(env);
-    }
+    support_enabled_ = GPR_GLOBAL_CONFIG_GET(grpc_enable_fork_support);
   }
   if (support_enabled_) {
     exec_ctx_state_ = grpc_core::New<internal::ExecCtxState>();
diff --git a/src/core/lib/gprpp/global_config.h b/src/core/lib/gprpp/global_config.h
new file mode 100644
index 0000000..a1bbf07
--- /dev/null
+++ b/src/core/lib/gprpp/global_config.h
@@ -0,0 +1,87 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_H
+#define GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_H
+
+#include <grpc/support/port_platform.h>
+
+#include <stdint.h>
+
+// --------------------------------------------------------------------
+// How to use global configuration variables:
+//
+// Defining config variables of a specified type:
+//   GPR_GLOBAL_CONFIG_DEFINE_*TYPE*(name, default_value, help);
+//
+// Supported TYPEs: BOOL, INT32, STRING
+//
+// It's recommended to use lowercase letters for 'name' like
+// regular variables. The builtin configuration system uses
+// environment variable and the name is converted to uppercase
+// when looking up the value. For example,
+// GPR_GLOBAL_CONFIG_DEFINE(grpc_latency) looks up the value with the
+// name, "GRPC_LATENCY".
+//
+// The variable initially has the specified 'default_value'
+// which must be an expression convertible to 'Type'.
+// 'default_value' may be evaluated 0 or more times,
+// and at an unspecified time; keep it
+// simple and usually free of side-effects.
+//
+// GPR_GLOBAL_CONFIG_DEFINE_*TYPE* should not be called in a C++ header.
+// It should be called at the top-level (outside any namespaces)
+// in a .cc file.
+//
+// Getting the variables:
+//   GPR_GLOBAL_CONFIG_GET(name)
+//
+// If error happens during getting variables, error messages will
+// be logged and default value will be returned.
+//
+// Setting the variables with new value:
+//   GPR_GLOBAL_CONFIG_SET(name, new_value)
+//
+// Declaring config variables for other modules to access:
+//   GPR_GLOBAL_CONFIG_DECLARE_*TYPE*(name)
+
+// --------------------------------------------------------------------
+// How to customize the global configuration system:
+//
+// How to read and write configuration value can be customized.
+// Builtin system uses environment variables but it can be extended to
+// support command-line flag, file, etc.
+//
+// To customize it, following macros should be redefined.
+//
+//   GPR_GLOBAL_CONFIG_DEFINE_BOOL
+//   GPR_GLOBAL_CONFIG_DEFINE_INT32
+//   GPR_GLOBAL_CONFIG_DEFINE_STRING
+//
+// These macros should define functions for getting and setting variable.
+// For example, GPR_GLOBAL_CONFIG_DEFINE_BOOL(test, ...) would define two
+// functions.
+//
+//   bool gpr_global_config_get_test();
+//   void gpr_global_config_set_test(bool value);
+
+#include "src/core/lib/gprpp/global_config_env.h"
+
+#include "src/core/lib/gprpp/global_config_custom.h"
+
+#endif /* GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_H */
diff --git a/src/core/lib/gprpp/global_config_custom.h b/src/core/lib/gprpp/global_config_custom.h
new file mode 100644
index 0000000..dd011fb
--- /dev/null
+++ b/src/core/lib/gprpp/global_config_custom.h
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_CUSTOM_H
+#define GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_CUSTOM_H
+
+// This is a placeholder for custom global configuration implementaion.
+// To use the custom one, please define following macros here.
+//
+//   GPR_GLOBAL_CONFIG_DEFINE_BOOL
+//   GPR_GLOBAL_CONFIG_DEFINE_INT32
+//   GPR_GLOBAL_CONFIG_DEFINE_STRING
+
+#endif /* GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_CUSTOM_H */
diff --git a/src/core/lib/gprpp/global_config_env.cc b/src/core/lib/gprpp/global_config_env.cc
new file mode 100644
index 0000000..fb14805
--- /dev/null
+++ b/src/core/lib/gprpp/global_config_env.cc
@@ -0,0 +1,135 @@
+/*
+ *
+ * Copyright 2019 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 <grpc/support/port_platform.h>
+
+#include "src/core/lib/gprpp/global_config_env.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gpr/string.h"
+
+#include <ctype.h>
+#include <string.h>
+
+namespace grpc_core {
+
+namespace {
+
+void DefaultGlobalConfigEnvErrorFunction(const char* error_message) {
+  gpr_log(GPR_ERROR, "%s", error_message);
+}
+
+GlobalConfigEnvErrorFunctionType g_global_config_env_error_func =
+    DefaultGlobalConfigEnvErrorFunction;
+
+void LogParsingError(const char* name, const char* value) {
+  char* error_message;
+  gpr_asprintf(&error_message,
+               "Illegal value '%s' specified for environment variable '%s'",
+               value, name);
+  (*g_global_config_env_error_func)(error_message);
+  gpr_free(error_message);
+}
+
+}  // namespace
+
+void SetGlobalConfigEnvErrorFunction(GlobalConfigEnvErrorFunctionType func) {
+  g_global_config_env_error_func = func;
+}
+
+UniquePtr<char> GlobalConfigEnv::GetValue() {
+  return UniquePtr<char>(gpr_getenv(GetName()));
+}
+
+void GlobalConfigEnv::SetValue(const char* value) {
+  gpr_setenv(GetName(), value);
+}
+
+void GlobalConfigEnv::Unset() { gpr_unsetenv(GetName()); }
+
+char* GlobalConfigEnv::GetName() {
+  // This makes sure that name_ is in a canonical form having uppercase
+  // letters. This is okay to be called serveral times.
+  for (char* c = name_; *c != 0; ++c) {
+    *c = toupper(*c);
+  }
+  return name_;
+}
+static_assert(std::is_trivially_destructible<GlobalConfigEnvBool>::value,
+              "GlobalConfigEnvBool needs to be trivially destructible.");
+
+bool GlobalConfigEnvBool::Get() {
+  UniquePtr<char> str = GetValue();
+  if (str == nullptr) {
+    return default_value_;
+  }
+  // parsing given value string.
+  bool result = false;
+  if (!gpr_parse_bool_value(str.get(), &result)) {
+    LogParsingError(GetName(), str.get());
+    result = default_value_;
+  }
+  return result;
+}
+
+void GlobalConfigEnvBool::Set(bool value) {
+  SetValue(value ? "true" : "false");
+}
+
+static_assert(std::is_trivially_destructible<GlobalConfigEnvInt32>::value,
+              "GlobalConfigEnvInt32 needs to be trivially destructible.");
+
+int32_t GlobalConfigEnvInt32::Get() {
+  UniquePtr<char> str = GetValue();
+  if (str == nullptr) {
+    return default_value_;
+  }
+  // parsing given value string.
+  char* end = str.get();
+  long result = strtol(str.get(), &end, 10);
+  if (*end != 0) {
+    LogParsingError(GetName(), str.get());
+    result = default_value_;
+  }
+  return static_cast<int32_t>(result);
+}
+
+void GlobalConfigEnvInt32::Set(int32_t value) {
+  char buffer[GPR_LTOA_MIN_BUFSIZE];
+  gpr_ltoa(value, buffer);
+  SetValue(buffer);
+}
+
+static_assert(std::is_trivially_destructible<GlobalConfigEnvString>::value,
+              "GlobalConfigEnvString needs to be trivially destructible.");
+
+UniquePtr<char> GlobalConfigEnvString::Get() {
+  UniquePtr<char> str = GetValue();
+  if (str == nullptr) {
+    return UniquePtr<char>(gpr_strdup(default_value_));
+  }
+  return str;
+}
+
+void GlobalConfigEnvString::Set(const char* value) { SetValue(value); }
+
+}  // namespace grpc_core
diff --git a/src/core/lib/gprpp/global_config_env.h b/src/core/lib/gprpp/global_config_env.h
new file mode 100644
index 0000000..3d30388
--- /dev/null
+++ b/src/core/lib/gprpp/global_config_env.h
@@ -0,0 +1,131 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_ENV_H
+#define GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_ENV_H
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/lib/gprpp/global_config_generic.h"
+#include "src/core/lib/gprpp/memory.h"
+
+namespace grpc_core {
+
+typedef void (*GlobalConfigEnvErrorFunctionType)(const char* error_message);
+
+/*
+ * Set global_config_env_error_function which is called when config system
+ * encounters errors such as parsing error. What the default function does
+ * is logging error message.
+ */
+void SetGlobalConfigEnvErrorFunction(GlobalConfigEnvErrorFunctionType func);
+
+// Base class for all classes to access environment variables.
+class GlobalConfigEnv {
+ protected:
+  // `name` should be writable and alive after constructor is called.
+  constexpr explicit GlobalConfigEnv(char* name) : name_(name) {}
+
+ public:
+  // Returns the value of `name` variable.
+  UniquePtr<char> GetValue();
+
+  // Sets the value of `name` variable.
+  void SetValue(const char* value);
+
+  // Unsets `name` variable.
+  void Unset();
+
+ protected:
+  char* GetName();
+
+ private:
+  char* name_;
+};
+
+class GlobalConfigEnvBool : public GlobalConfigEnv {
+ public:
+  constexpr GlobalConfigEnvBool(char* name, bool default_value)
+      : GlobalConfigEnv(name), default_value_(default_value) {}
+
+  bool Get();
+  void Set(bool value);
+
+ private:
+  bool default_value_;
+};
+
+class GlobalConfigEnvInt32 : public GlobalConfigEnv {
+ public:
+  constexpr GlobalConfigEnvInt32(char* name, int32_t default_value)
+      : GlobalConfigEnv(name), default_value_(default_value) {}
+
+  int32_t Get();
+  void Set(int32_t value);
+
+ private:
+  int32_t default_value_;
+};
+
+class GlobalConfigEnvString : public GlobalConfigEnv {
+ public:
+  constexpr GlobalConfigEnvString(char* name, const char* default_value)
+      : GlobalConfigEnv(name), default_value_(default_value) {}
+
+  UniquePtr<char> Get();
+  void Set(const char* value);
+
+ private:
+  const char* default_value_;
+};
+
+}  // namespace grpc_core
+
+// Macros for defining global config instances using environment variables.
+// This defines a GlobalConfig*Type* instance with arguments for
+// mutable variable name and default value.
+// Mutable name (g_env_str_##name) is here for having an array
+// for the canonical name without dynamic allocation.
+// `help` argument is ignored for this implementation.
+
+#define GPR_GLOBAL_CONFIG_DEFINE_BOOL(name, default_value, help)         \
+  static char g_env_str_##name[] = #name;                                \
+  static ::grpc_core::GlobalConfigEnvBool g_env_##name(g_env_str_##name, \
+                                                       default_value);   \
+  bool gpr_global_config_get_##name() { return g_env_##name.Get(); }     \
+  void gpr_global_config_set_##name(bool value) { g_env_##name.Set(value); }
+
+#define GPR_GLOBAL_CONFIG_DEFINE_INT32(name, default_value, help)         \
+  static char g_env_str_##name[] = #name;                                 \
+  static ::grpc_core::GlobalConfigEnvInt32 g_env_##name(g_env_str_##name, \
+                                                        default_value);   \
+  int32_t gpr_global_config_get_##name() { return g_env_##name.Get(); }   \
+  void gpr_global_config_set_##name(int32_t value) { g_env_##name.Set(value); }
+
+#define GPR_GLOBAL_CONFIG_DEFINE_STRING(name, default_value, help)         \
+  static char g_env_str_##name[] = #name;                                  \
+  static ::grpc_core::GlobalConfigEnvString g_env_##name(g_env_str_##name, \
+                                                         default_value);   \
+  ::grpc_core::UniquePtr<char> gpr_global_config_get_##name() {            \
+    return g_env_##name.Get();                                             \
+  }                                                                        \
+  void gpr_global_config_set_##name(const char* value) {                   \
+    g_env_##name.Set(value);                                               \
+  }
+
+#endif /* GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_ENV_H */
diff --git a/src/core/lib/gprpp/global_config_generic.h b/src/core/lib/gprpp/global_config_generic.h
new file mode 100644
index 0000000..d3e3e2a
--- /dev/null
+++ b/src/core/lib/gprpp/global_config_generic.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_GENERIC_H
+#define GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_GENERIC_H
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/lib/gprpp/memory.h"
+
+#include <stdint.h>
+
+#define GPR_GLOBAL_CONFIG_GET(name) gpr_global_config_get_##name()
+
+#define GPR_GLOBAL_CONFIG_SET(name, value) gpr_global_config_set_##name(value)
+
+#define GPR_GLOBAL_CONFIG_DECLARE_BOOL(name)  \
+  extern bool gpr_global_config_get_##name(); \
+  extern void gpr_global_config_set_##name(bool value)
+
+#define GPR_GLOBAL_CONFIG_DECLARE_INT32(name)    \
+  extern int32_t gpr_global_config_get_##name(); \
+  extern void gpr_global_config_set_##name(int32_t value)
+
+#define GPR_GLOBAL_CONFIG_DECLARE_STRING(name)                      \
+  extern grpc_core::UniquePtr<char> gpr_global_config_get_##name(); \
+  extern void gpr_global_config_set_##name(const char* value)
+
+#endif /* GRPC_CORE_LIB_GPRPP_GLOBAL_CONFIG_GENERIC_H */
diff --git a/src/core/lib/gprpp/map.h b/src/core/lib/gprpp/map.h
new file mode 100644
index 0000000..b210c26
--- /dev/null
+++ b/src/core/lib/gprpp/map.h
@@ -0,0 +1,419 @@
+/*
+ *
+ * Copyright 2017 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_GPRPP_MAP_H
+#define GRPC_CORE_LIB_GPRPP_MAP_H
+
+#include <grpc/support/port_platform.h>
+
+#include <string.h>
+#include <functional>
+#include <iterator>
+#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/gprpp/pair.h"
+
+namespace grpc_core {
+struct StringLess {
+  bool operator()(const char* a, const char* b) const {
+    return strcmp(a, b) < 0;
+  }
+  bool operator()(const UniquePtr<char>& k1, const UniquePtr<char>& k2) {
+    return strcmp(k1.get(), k2.get()) < 0;
+  }
+};
+
+namespace testing {
+class MapTest;
+}
+
+// Alternative map implementation for grpc_core
+template <class Key, class T, class Compare = std::less<Key>>
+class Map {
+ public:
+  typedef Key key_type;
+  typedef T mapped_type;
+  typedef Pair<key_type, mapped_type> value_type;
+  typedef Compare key_compare;
+  class iterator;
+
+  ~Map() { clear(); }
+
+  T& operator[](key_type&& key);
+  T& operator[](const key_type& key);
+  iterator find(const key_type& k);
+  size_t erase(const key_type& key);
+  // Removes the current entry and points to the next one
+  iterator erase(iterator iter);
+
+  size_t size() { return size_; }
+
+  template <class... Args>
+  Pair<iterator, bool> emplace(Args&&... args);
+
+  Pair<iterator, bool> insert(value_type&& pair) {
+    return emplace(std::move(pair));
+  }
+
+  Pair<iterator, bool> insert(const value_type& pair) { return emplace(pair); }
+
+  bool empty() const { return root_ == nullptr; }
+
+  void clear() {
+    auto iter = begin();
+    while (!empty()) {
+      iter = erase(iter);
+    }
+  }
+
+  iterator begin() {
+    Entry* curr = GetMinEntry(root_);
+    return iterator(this, curr);
+  }
+
+  iterator end() { return iterator(this, nullptr); }
+
+ private:
+  friend class testing::MapTest;
+  struct Entry {
+    explicit Entry(value_type&& pair) : pair(std::move(pair)) {}
+    value_type pair;
+    Entry* left = nullptr;
+    Entry* right = nullptr;
+    int32_t height = 1;
+  };
+
+  static int32_t EntryHeight(const Entry* e) {
+    return e == nullptr ? 0 : e->height;
+  }
+
+  static Entry* GetMinEntry(Entry* e);
+  Entry* InOrderSuccessor(const Entry* e) const;
+  static Entry* RotateLeft(Entry* e);
+  static Entry* RotateRight(Entry* e);
+  static Entry* RebalanceTreeAfterInsertion(Entry* root, const key_type& k);
+  static Entry* RebalanceTreeAfterDeletion(Entry* root);
+  // Returns a pair with the first value being an iterator pointing to the
+  // inserted entry and the second value being the new root of the subtree
+  // after a rebalance
+  Pair<iterator, Entry*> InsertRecursive(Entry* root, value_type&& p);
+  static Entry* RemoveRecursive(Entry* root, const key_type& k);
+  // Return 0 if lhs = rhs
+  //        1 if lhs > rhs
+  //       -1 if lhs < rhs
+  static int CompareKeys(const Key& lhs, const Key& rhs);
+
+  Entry* root_ = nullptr;
+  size_t size_ = 0;
+};
+
+template <class Key, class T, class Compare>
+class Map<Key, T, Compare>::iterator
+    : public std::iterator<std::input_iterator_tag, Pair<Key, T>, int32_t,
+                           Pair<Key, T>*, Pair<Key, T>&> {
+ public:
+  iterator(const iterator& iter) : curr_(iter.curr_), map_(iter.map_) {}
+  bool operator==(const iterator& rhs) const { return (curr_ == rhs.curr_); }
+  bool operator!=(const iterator& rhs) const { return (curr_ != rhs.curr_); }
+
+  iterator& operator++() {
+    curr_ = map_->InOrderSuccessor(curr_);
+    return *this;
+  }
+
+  iterator operator++(int) {
+    Entry* prev = curr_;
+    curr_ = map_->InOrderSuccessor(curr_);
+    return iterator(map_, prev);
+  }
+
+  iterator& operator=(const iterator& other) {
+    if (this != &other) {
+      this->curr_ = other.curr_;
+      this->map_ = other.map_;
+    }
+    return *this;
+  }
+
+  // operator*()
+  value_type& operator*() { return curr_->pair; }
+  const value_type& operator*() const { return curr_->pair; }
+
+  // operator->()
+  value_type* operator->() { return &curr_->pair; }
+  value_type const* operator->() const { return &curr_->pair; }
+
+ private:
+  friend class Map<key_type, mapped_type, key_compare>;
+  using GrpcMap = typename ::grpc_core::Map<Key, T, Compare>;
+  iterator(GrpcMap* map, Entry* curr) : curr_(curr), map_(map) {}
+  Entry* curr_;
+  GrpcMap* map_;
+};
+
+template <class Key, class T, class Compare>
+T& Map<Key, T, Compare>::operator[](key_type&& key) {
+  auto iter = find(key);
+  if (iter == end()) {
+    return emplace(std::move(key), T()).first->second;
+  }
+  return iter->second;
+}
+
+template <class Key, class T, class Compare>
+T& Map<Key, T, Compare>::operator[](const key_type& key) {
+  auto iter = find(key);
+  if (iter == end()) {
+    return emplace(key, T()).first->second;
+  }
+  return iter->second;
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::iterator Map<Key, T, Compare>::find(
+    const key_type& k) {
+  Entry* iter = root_;
+  while (iter != nullptr) {
+    int comp = CompareKeys(iter->pair.first, k);
+    if (comp == 0) {
+      return iterator(this, iter);
+    } else if (comp < 0) {
+      iter = iter->right;
+    } else {
+      iter = iter->left;
+    }
+  }
+  return end();
+}
+
+template <class Key, class T, class Compare>
+template <class... Args>
+typename ::grpc_core::Pair<typename Map<Key, T, Compare>::iterator, bool>
+Map<Key, T, Compare>::emplace(Args&&... args) {
+  Pair<key_type, mapped_type> pair(std::forward<Args>(args)...);
+  iterator ret = find(pair.first);
+  bool insertion = false;
+  if (ret == end()) {
+    Pair<iterator, Entry*> p = InsertRecursive(root_, std::move(pair));
+    root_ = p.second;
+    ret = p.first;
+    insertion = true;
+    size_++;
+  }
+  return MakePair(ret, insertion);
+}
+
+template <class Key, class T, class Compare>
+size_t Map<Key, T, Compare>::erase(const key_type& key) {
+  iterator it = find(key);
+  if (it == end()) return 0;
+  erase(it);
+  return 1;
+}
+
+// TODO(mhaidry): Modify erase to use the iterator location
+// to create an efficient erase method
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::iterator Map<Key, T, Compare>::erase(
+    iterator iter) {
+  if (iter == end()) return iter;
+  key_type& del_key = iter->first;
+  iter++;
+  root_ = RemoveRecursive(root_, del_key);
+  size_--;
+  return iter;
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::Entry* Map<Key, T, Compare>::InOrderSuccessor(
+    const Entry* e) const {
+  if (e->right != nullptr) {
+    return GetMinEntry(e->right);
+  }
+  Entry* successor = nullptr;
+  Entry* iter = root_;
+  while (iter != nullptr) {
+    int comp = CompareKeys(iter->pair.first, e->pair.first);
+    if (comp > 0) {
+      successor = iter;
+      iter = iter->left;
+    } else if (comp < 0) {
+      iter = iter->right;
+    } else
+      break;
+  }
+  return successor;
+}
+
+// Returns a pair with the first value being an iterator pointing to the
+// inserted entry and the second value being the new root of the subtree
+// after a rebalance
+template <class Key, class T, class Compare>
+typename ::grpc_core::Pair<typename Map<Key, T, Compare>::iterator,
+                           typename Map<Key, T, Compare>::Entry*>
+Map<Key, T, Compare>::InsertRecursive(Entry* root, value_type&& p) {
+  if (root == nullptr) {
+    Entry* e = New<Entry>(std::move(p));
+    return MakePair(iterator(this, e), e);
+  }
+  int comp = CompareKeys(root->pair.first, p.first);
+  if (comp > 0) {
+    Pair<iterator, Entry*> ret = InsertRecursive(root->left, std::move(p));
+    root->left = ret.second;
+    ret.second = RebalanceTreeAfterInsertion(root, ret.first->first);
+    return ret;
+  } else if (comp < 0) {
+    Pair<iterator, Entry*> ret = InsertRecursive(root->right, std::move(p));
+    root->right = ret.second;
+    ret.second = RebalanceTreeAfterInsertion(root, ret.first->first);
+    return ret;
+  } else {
+    root->pair = std::move(p);
+    return MakePair(iterator(this, root), root);
+  }
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::Entry* Map<Key, T, Compare>::GetMinEntry(
+    Entry* e) {
+  if (e != nullptr) {
+    while (e->left != nullptr) {
+      e = e->left;
+    }
+  }
+  return e;
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::Entry* Map<Key, T, Compare>::RotateLeft(
+    Entry* e) {
+  Entry* rightChild = e->right;
+  Entry* rightLeftChild = rightChild->left;
+  rightChild->left = e;
+  e->right = rightLeftChild;
+  e->height = 1 + GPR_MAX(EntryHeight(e->left), EntryHeight(e->right));
+  rightChild->height = 1 + GPR_MAX(EntryHeight(rightChild->left),
+                                   EntryHeight(rightChild->right));
+  return rightChild;
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::Entry* Map<Key, T, Compare>::RotateRight(
+    Entry* e) {
+  Entry* leftChild = e->left;
+  Entry* leftRightChild = leftChild->right;
+  leftChild->right = e;
+  e->left = leftRightChild;
+  e->height = 1 + GPR_MAX(EntryHeight(e->left), EntryHeight(e->right));
+  leftChild->height =
+      1 + GPR_MAX(EntryHeight(leftChild->left), EntryHeight(leftChild->right));
+  return leftChild;
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::Entry*
+Map<Key, T, Compare>::RebalanceTreeAfterInsertion(Entry* root,
+                                                  const key_type& k) {
+  root->height = 1 + GPR_MAX(EntryHeight(root->left), EntryHeight(root->right));
+  int32_t heightDifference = EntryHeight(root->left) - EntryHeight(root->right);
+  if (heightDifference > 1 && CompareKeys(root->left->pair.first, k) > 0) {
+    return RotateRight(root);
+  }
+  if (heightDifference < -1 && CompareKeys(root->right->pair.first, k) < 0) {
+    return RotateLeft(root);
+  }
+  if (heightDifference > 1 && CompareKeys(root->left->pair.first, k) < 0) {
+    root->left = RotateLeft(root->left);
+    return RotateRight(root);
+  }
+  if (heightDifference < -1 && CompareKeys(root->right->pair.first, k) > 0) {
+    root->right = RotateRight(root->right);
+    return RotateLeft(root);
+  }
+  return root;
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::Entry*
+Map<Key, T, Compare>::RebalanceTreeAfterDeletion(Entry* root) {
+  root->height = 1 + GPR_MAX(EntryHeight(root->left), EntryHeight(root->right));
+  int32_t heightDifference = EntryHeight(root->left) - EntryHeight(root->right);
+  if (heightDifference > 1) {
+    int leftHeightDifference =
+        EntryHeight(root->left->left) - EntryHeight(root->left->right);
+    if (leftHeightDifference < 0) {
+      root->left = RotateLeft(root->left);
+    }
+    return RotateRight(root);
+  }
+  if (heightDifference < -1) {
+    int rightHeightDifference =
+        EntryHeight(root->right->left) - EntryHeight(root->right->right);
+    if (rightHeightDifference > 0) {
+      root->right = RotateRight(root->right);
+    }
+    return RotateLeft(root);
+  }
+  return root;
+}
+
+template <class Key, class T, class Compare>
+typename Map<Key, T, Compare>::Entry* Map<Key, T, Compare>::RemoveRecursive(
+    Entry* root, const key_type& k) {
+  if (root == nullptr) return root;
+  int comp = CompareKeys(root->pair.first, k);
+  if (comp > 0) {
+    root->left = RemoveRecursive(root->left, k);
+  } else if (comp < 0) {
+    root->right = RemoveRecursive(root->right, k);
+  } else {
+    Entry* ret;
+    if (root->left == nullptr) {
+      ret = root->right;
+      Delete(root);
+      return ret;
+    } else if (root->right == nullptr) {
+      ret = root->left;
+      Delete(root);
+      return ret;
+    } else {
+      ret = root->right;
+      while (ret->left != nullptr) {
+        ret = ret->left;
+      }
+      root->pair.swap(ret->pair);
+      root->right = RemoveRecursive(root->right, ret->pair.first);
+    }
+  }
+  return RebalanceTreeAfterDeletion(root);
+}
+
+template <class Key, class T, class Compare>
+int Map<Key, T, Compare>::CompareKeys(const key_type& lhs,
+                                      const key_type& rhs) {
+  key_compare compare;
+  bool left_comparison = compare(lhs, rhs);
+  bool right_comparison = compare(rhs, lhs);
+  // Both values are equal
+  if (!left_comparison && !right_comparison) {
+    return 0;
+  }
+  return left_comparison ? -1 : 1;
+}
+}  // namespace grpc_core
+#endif /* GRPC_CORE_LIB_GPRPP_MAP_H */
diff --git a/src/core/lib/gprpp/mutex_lock.h b/src/core/lib/gprpp/mutex_lock.h
deleted file mode 100644
index 54751d5..0000000
--- a/src/core/lib/gprpp/mutex_lock.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *
- * Copyright 2018 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#ifndef GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H
-#define GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H
-
-#include <grpc/support/port_platform.h>
-
-#include <grpc/support/sync.h>
-
-namespace grpc_core {
-
-class MutexLock {
- public:
-  explicit MutexLock(gpr_mu* mu) : mu_(mu) { gpr_mu_lock(mu); }
-  ~MutexLock() { gpr_mu_unlock(mu_); }
-
-  MutexLock(const MutexLock&) = delete;
-  MutexLock& operator=(const MutexLock&) = delete;
-
- private:
-  gpr_mu* const mu_;
-};
-
-}  // namespace grpc_core
-
-#endif /* GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H */
diff --git a/src/core/lib/gprpp/optional.h b/src/core/lib/gprpp/optional.h
index a8e3ce1..ab5f863 100644
--- a/src/core/lib/gprpp/optional.h
+++ b/src/core/lib/gprpp/optional.h
@@ -26,6 +26,7 @@
 template <typename T>
 class Optional {
  public:
+  Optional() : value_() {}
   void set(const T& val) {
     value_ = val;
     set_ = true;
diff --git a/src/core/lib/gprpp/orphanable.h b/src/core/lib/gprpp/orphanable.h
index dda5026..2e467e4 100644
--- a/src/core/lib/gprpp/orphanable.h
+++ b/src/core/lib/gprpp/orphanable.h
@@ -110,12 +110,12 @@
   }
 
   void Unref() {
-    if (refs_.Unref()) {
+    if (GPR_UNLIKELY(refs_.Unref())) {
       Delete(static_cast<Child*>(this));
     }
   }
   void Unref(const DebugLocation& location, const char* reason) {
-    if (refs_.Unref(location, reason)) {
+    if (GPR_UNLIKELY(refs_.Unref(location, reason))) {
       Delete(static_cast<Child*>(this));
     }
   }
diff --git a/src/core/lib/gprpp/pair.h b/src/core/lib/gprpp/pair.h
new file mode 100644
index 0000000..ca8294c
--- /dev/null
+++ b/src/core/lib/gprpp/pair.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright 2017 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_GPRPP_PAIR_H
+#define GRPC_CORE_LIB_GPRPP_PAIR_H
+
+#include <grpc/support/port_platform.h>
+
+#include <utility>
+
+namespace grpc_core {
+template <class T1, class T2>
+using Pair = std::pair<T1, T2>;
+
+template <class T1, class T2>
+inline Pair<typename std::decay<T1>::type, typename std::decay<T2>::type>
+MakePair(T1&& u, T2&& v) {
+  typedef typename std::decay<T1>::type V1;
+  typedef typename std::decay<T2>::type V2;
+  return Pair<V1, V2>(std::forward<T1>(u), std::forward<T2>(v));
+}
+}  // namespace grpc_core
+#endif /* GRPC_CORE_LIB_GPRPP_PAIR_H */
diff --git a/src/core/lib/gprpp/ref_counted.h b/src/core/lib/gprpp/ref_counted.h
index 4937f31..83e9429 100644
--- a/src/core/lib/gprpp/ref_counted.h
+++ b/src/core/lib/gprpp/ref_counted.h
@@ -123,6 +123,22 @@
     RefNonZero();
   }
 
+  bool RefIfNonZero() { return value_.IncrementIfNonzero(); }
+
+  bool RefIfNonZero(const DebugLocation& location, const char* reason) {
+#ifndef NDEBUG
+    if (location.Log() && trace_flag_ != nullptr && trace_flag_->enabled()) {
+      const RefCount::Value old_refs = get();
+      gpr_log(GPR_INFO,
+              "%s:%p %s:%d ref_if_non_zero "
+              "%" PRIdPTR " -> %" PRIdPTR " %s",
+              trace_flag_->name(), this, location.file(), location.line(),
+              old_refs, old_refs + 1, reason);
+    }
+#endif
+    return RefIfNonZero();
+  }
+
   // Decrements the ref-count and returns true if the ref-count reaches 0.
   bool Unref() {
     const Value prior = value_.FetchSub(1, MemoryOrder::ACQ_REL);
@@ -195,12 +211,12 @@
   // private, since it will only be used by RefCountedPtr<>, which is a
   // friend of this class.
   void Unref() {
-    if (refs_.Unref()) {
+    if (GPR_UNLIKELY(refs_.Unref())) {
       Delete(static_cast<Child*>(this));
     }
   }
   void Unref(const DebugLocation& location, const char* reason) {
-    if (refs_.Unref(location, reason)) {
+    if (GPR_UNLIKELY(refs_.Unref(location, reason))) {
       Delete(static_cast<Child*>(this));
     }
   }
diff --git a/src/core/lib/gprpp/sync.h b/src/core/lib/gprpp/sync.h
new file mode 100644
index 0000000..895ca60
--- /dev/null
+++ b/src/core/lib/gprpp/sync.h
@@ -0,0 +1,126 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_GPRPP_SYNC_H
+#define GRPC_CORE_LIB_GPRPP_SYNC_H
+
+#include <grpc/impl/codegen/port_platform.h>
+
+#include <grpc/impl/codegen/log.h>
+#include <grpc/impl/codegen/sync.h>
+#include <grpc/support/sync.h>
+#include <grpc/support/time.h>
+
+// The core library is not accessible in C++ codegen headers, and vice versa.
+// Thus, we need to have duplicate headers with similar functionality.
+// Make sure any change to this file is also reflected in
+// include/grpcpp/impl/codegen/sync.h.
+//
+// Whenever possible, prefer using this file over <grpcpp/impl/codegen/sync.h>
+// since this file doesn't rely on g_core_codegen_interface and hence does not
+// pay the costs of virtual function calls.
+
+namespace grpc_core {
+
+class Mutex {
+ public:
+  Mutex() { gpr_mu_init(&mu_); }
+  ~Mutex() { gpr_mu_destroy(&mu_); }
+
+  Mutex(const Mutex&) = delete;
+  Mutex& operator=(const Mutex&) = delete;
+
+  gpr_mu* get() { return &mu_; }
+  const gpr_mu* get() const { return &mu_; }
+
+ private:
+  gpr_mu mu_;
+};
+
+// MutexLock is a std::
+class MutexLock {
+ public:
+  explicit MutexLock(Mutex* mu) : mu_(mu->get()) { gpr_mu_lock(mu_); }
+  explicit MutexLock(gpr_mu* mu) : mu_(mu) { gpr_mu_lock(mu_); }
+  ~MutexLock() { gpr_mu_unlock(mu_); }
+
+  MutexLock(const MutexLock&) = delete;
+  MutexLock& operator=(const MutexLock&) = delete;
+
+ private:
+  gpr_mu* const mu_;
+};
+
+class ReleasableMutexLock {
+ public:
+  explicit ReleasableMutexLock(Mutex* mu) : mu_(mu->get()) { gpr_mu_lock(mu_); }
+  explicit ReleasableMutexLock(gpr_mu* mu) : mu_(mu) { gpr_mu_lock(mu_); }
+  ~ReleasableMutexLock() {
+    if (!released_) gpr_mu_unlock(mu_);
+  }
+
+  ReleasableMutexLock(const ReleasableMutexLock&) = delete;
+  ReleasableMutexLock& operator=(const ReleasableMutexLock&) = delete;
+
+  void Lock() {
+    GPR_DEBUG_ASSERT(released_);
+    gpr_mu_lock(mu_);
+    released_ = false;
+  }
+
+  void Unlock() {
+    GPR_DEBUG_ASSERT(!released_);
+    released_ = true;
+    gpr_mu_unlock(mu_);
+  }
+
+ private:
+  gpr_mu* const mu_;
+  bool released_ = false;
+};
+
+class CondVar {
+ public:
+  CondVar() { gpr_cv_init(&cv_); }
+  ~CondVar() { gpr_cv_destroy(&cv_); }
+
+  CondVar(const CondVar&) = delete;
+  CondVar& operator=(const CondVar&) = delete;
+
+  void Signal() { gpr_cv_signal(&cv_); }
+  void Broadcast() { gpr_cv_broadcast(&cv_); }
+
+  int Wait(Mutex* mu) { return Wait(mu, gpr_inf_future(GPR_CLOCK_REALTIME)); }
+  int Wait(Mutex* mu, const gpr_timespec& deadline) {
+    return gpr_cv_wait(&cv_, mu->get(), deadline);
+  }
+
+  template <typename Predicate>
+  void WaitUntil(Mutex* mu, Predicate pred) {
+    while (!pred()) {
+      Wait(mu, gpr_inf_future(GPR_CLOCK_REALTIME));
+    }
+  }
+
+ private:
+  gpr_cv cv_;
+};
+
+}  // namespace grpc_core
+
+#endif /* GRPC_CORE_LIB_GPRPP_SYNC_H */
diff --git a/src/core/lib/http/parser.cc b/src/core/lib/http/parser.cc
index 7ca1cc9..58608da 100644
--- a/src/core/lib/http/parser.cc
+++ b/src/core/lib/http/parser.cc
@@ -300,7 +300,7 @@
     case GRPC_HTTP_FIRST_LINE:
     case GRPC_HTTP_HEADERS:
       if (parser->cur_line_length >= GRPC_HTTP_PARSER_MAX_HEADER_LENGTH) {
-        if (grpc_http1_trace.enabled())
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_http1_trace))
           gpr_log(GPR_ERROR, "HTTP header max line length (%d) exceeded",
                   GRPC_HTTP_PARSER_MAX_HEADER_LENGTH);
         return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
diff --git a/src/core/lib/iomgr/call_combiner.cc b/src/core/lib/iomgr/call_combiner.cc
index 6b5759a..6a4e85d 100644
--- a/src/core/lib/iomgr/call_combiner.cc
+++ b/src/core/lib/iomgr/call_combiner.cc
@@ -26,23 +26,43 @@
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/profiling/timers.h"
 
-grpc_core::TraceFlag grpc_call_combiner_trace(false, "call_combiner");
+namespace grpc_core {
 
-static grpc_error* decode_cancel_state_error(gpr_atm cancel_state) {
+TraceFlag grpc_call_combiner_trace(false, "call_combiner");
+
+namespace {
+
+grpc_error* DecodeCancelStateError(gpr_atm cancel_state) {
   if (cancel_state & 1) {
     return (grpc_error*)(cancel_state & ~static_cast<gpr_atm>(1));
   }
   return GRPC_ERROR_NONE;
 }
 
-static gpr_atm encode_cancel_state_error(grpc_error* error) {
+gpr_atm EncodeCancelStateError(grpc_error* error) {
   return static_cast<gpr_atm>(1) | (gpr_atm)error;
 }
 
+}  // namespace
+
+CallCombiner::CallCombiner() {
+  gpr_atm_no_barrier_store(&cancel_state_, 0);
+  gpr_atm_no_barrier_store(&size_, 0);
+  gpr_mpscq_init(&queue_);
 #ifdef GRPC_TSAN_ENABLED
-static void tsan_closure(void* user_data, grpc_error* error) {
-  grpc_call_combiner* call_combiner =
-      static_cast<grpc_call_combiner*>(user_data);
+  GRPC_CLOSURE_INIT(&tsan_closure_, TsanClosure, this,
+                    grpc_schedule_on_exec_ctx);
+#endif
+}
+
+CallCombiner::~CallCombiner() {
+  gpr_mpscq_destroy(&queue_);
+  GRPC_ERROR_UNREF(DecodeCancelStateError(cancel_state_));
+}
+
+#ifdef GRPC_TSAN_ENABLED
+void CallCombiner::TsanClosure(void* arg, grpc_error* error) {
+  CallCombiner* self = static_cast<CallCombiner*>(arg);
   // We ref-count the lock, and check if it's already taken.
   // If it was taken, we should do nothing. Otherwise, we will mark it as
   // locked. Note that if two different threads try to do this, only one of
@@ -51,18 +71,18 @@
   // TSAN will correctly produce an error.
   //
   // TODO(soheil): This only covers the callbacks scheduled by
-  //               grpc_call_combiner_(start|finish). If in the future, a
-  //               callback gets scheduled using other mechanisms, we will need
-  //               to add APIs to externally lock call combiners.
-  grpc_core::RefCountedPtr<grpc_call_combiner::TsanLock> lock =
-      call_combiner->tsan_lock;
+  //               CallCombiner::Start() and CallCombiner::Stop().
+  //               If in the future, a callback gets scheduled using other
+  //               mechanisms, we will need to add APIs to externally lock
+  //               call combiners.
+  RefCountedPtr<TsanLock> lock = self->tsan_lock_;
   bool prev = false;
   if (lock->taken.compare_exchange_strong(prev, true)) {
     TSAN_ANNOTATE_RWLOCK_ACQUIRED(&lock->taken, true);
   } else {
     lock.reset();
   }
-  GRPC_CLOSURE_RUN(call_combiner->original_closure, GRPC_ERROR_REF(error));
+  GRPC_CLOSURE_RUN(self->original_closure_, GRPC_ERROR_REF(error));
   if (lock != nullptr) {
     TSAN_ANNOTATE_RWLOCK_RELEASED(&lock->taken, true);
     bool prev = true;
@@ -71,34 +91,17 @@
 }
 #endif
 
-static void call_combiner_sched_closure(grpc_call_combiner* call_combiner,
-                                        grpc_closure* closure,
-                                        grpc_error* error) {
+void CallCombiner::ScheduleClosure(grpc_closure* closure, grpc_error* error) {
 #ifdef GRPC_TSAN_ENABLED
-  call_combiner->original_closure = closure;
-  GRPC_CLOSURE_SCHED(&call_combiner->tsan_closure, error);
+  original_closure_ = closure;
+  GRPC_CLOSURE_SCHED(&tsan_closure_, error);
 #else
   GRPC_CLOSURE_SCHED(closure, error);
 #endif
 }
 
-void grpc_call_combiner_init(grpc_call_combiner* call_combiner) {
-  gpr_atm_no_barrier_store(&call_combiner->cancel_state, 0);
-  gpr_atm_no_barrier_store(&call_combiner->size, 0);
-  gpr_mpscq_init(&call_combiner->queue);
-#ifdef GRPC_TSAN_ENABLED
-  GRPC_CLOSURE_INIT(&call_combiner->tsan_closure, tsan_closure, call_combiner,
-                    grpc_schedule_on_exec_ctx);
-#endif
-}
-
-void grpc_call_combiner_destroy(grpc_call_combiner* call_combiner) {
-  gpr_mpscq_destroy(&call_combiner->queue);
-  GRPC_ERROR_UNREF(decode_cancel_state_error(call_combiner->cancel_state));
-}
-
 #ifndef NDEBUG
-#define DEBUG_ARGS , const char *file, int line
+#define DEBUG_ARGS const char *file, int line,
 #define DEBUG_FMT_STR "%s:%d: "
 #define DEBUG_FMT_ARGS , file, line
 #else
@@ -107,123 +110,113 @@
 #define DEBUG_FMT_ARGS
 #endif
 
-void grpc_call_combiner_start(grpc_call_combiner* call_combiner,
-                              grpc_closure* closure,
-                              grpc_error* error DEBUG_ARGS,
-                              const char* reason) {
-  GPR_TIMER_SCOPE("call_combiner_start", 0);
-  if (grpc_call_combiner_trace.enabled()) {
+void CallCombiner::Start(grpc_closure* closure, grpc_error* error,
+                         DEBUG_ARGS const char* reason) {
+  GPR_TIMER_SCOPE("CallCombiner::Start", 0);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
     gpr_log(GPR_INFO,
-            "==> grpc_call_combiner_start() [%p] closure=%p [" DEBUG_FMT_STR
+            "==> CallCombiner::Start() [%p] closure=%p [" DEBUG_FMT_STR
             "%s] error=%s",
-            call_combiner, closure DEBUG_FMT_ARGS, reason,
-            grpc_error_string(error));
+            this, closure DEBUG_FMT_ARGS, reason, grpc_error_string(error));
   }
-  size_t prev_size = static_cast<size_t>(
-      gpr_atm_full_fetch_add(&call_combiner->size, (gpr_atm)1));
-  if (grpc_call_combiner_trace.enabled()) {
+  size_t prev_size =
+      static_cast<size_t>(gpr_atm_full_fetch_add(&size_, (gpr_atm)1));
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
     gpr_log(GPR_INFO, "  size: %" PRIdPTR " -> %" PRIdPTR, prev_size,
             prev_size + 1);
   }
   GRPC_STATS_INC_CALL_COMBINER_LOCKS_SCHEDULED_ITEMS();
   if (prev_size == 0) {
     GRPC_STATS_INC_CALL_COMBINER_LOCKS_INITIATED();
-
     GPR_TIMER_MARK("call_combiner_initiate", 0);
-    if (grpc_call_combiner_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
       gpr_log(GPR_INFO, "  EXECUTING IMMEDIATELY");
     }
     // Queue was empty, so execute this closure immediately.
-    call_combiner_sched_closure(call_combiner, closure, error);
+    ScheduleClosure(closure, error);
   } else {
-    if (grpc_call_combiner_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
       gpr_log(GPR_INFO, "  QUEUING");
     }
     // Queue was not empty, so add closure to queue.
     closure->error_data.error = error;
-    gpr_mpscq_push(&call_combiner->queue,
-                   reinterpret_cast<gpr_mpscq_node*>(closure));
+    gpr_mpscq_push(&queue_, reinterpret_cast<gpr_mpscq_node*>(closure));
   }
 }
 
-void grpc_call_combiner_stop(grpc_call_combiner* call_combiner DEBUG_ARGS,
-                             const char* reason) {
-  GPR_TIMER_SCOPE("call_combiner_stop", 0);
-  if (grpc_call_combiner_trace.enabled()) {
-    gpr_log(GPR_INFO,
-            "==> grpc_call_combiner_stop() [%p] [" DEBUG_FMT_STR "%s]",
-            call_combiner DEBUG_FMT_ARGS, reason);
+void CallCombiner::Stop(DEBUG_ARGS const char* reason) {
+  GPR_TIMER_SCOPE("CallCombiner::Stop", 0);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
+    gpr_log(GPR_INFO, "==> CallCombiner::Stop() [%p] [" DEBUG_FMT_STR "%s]",
+            this DEBUG_FMT_ARGS, reason);
   }
-  size_t prev_size = static_cast<size_t>(
-      gpr_atm_full_fetch_add(&call_combiner->size, (gpr_atm)-1));
-  if (grpc_call_combiner_trace.enabled()) {
+  size_t prev_size =
+      static_cast<size_t>(gpr_atm_full_fetch_add(&size_, (gpr_atm)-1));
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
     gpr_log(GPR_INFO, "  size: %" PRIdPTR " -> %" PRIdPTR, prev_size,
             prev_size - 1);
   }
   GPR_ASSERT(prev_size >= 1);
   if (prev_size > 1) {
     while (true) {
-      if (grpc_call_combiner_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
         gpr_log(GPR_INFO, "  checking queue");
       }
       bool empty;
       grpc_closure* closure = reinterpret_cast<grpc_closure*>(
-          gpr_mpscq_pop_and_check_end(&call_combiner->queue, &empty));
+          gpr_mpscq_pop_and_check_end(&queue_, &empty));
       if (closure == nullptr) {
         // This can happen either due to a race condition within the mpscq
-        // code or because of a race with grpc_call_combiner_start().
-        if (grpc_call_combiner_trace.enabled()) {
+        // code or because of a race with Start().
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
           gpr_log(GPR_INFO, "  queue returned no result; checking again");
         }
         continue;
       }
-      if (grpc_call_combiner_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
         gpr_log(GPR_INFO, "  EXECUTING FROM QUEUE: closure=%p error=%s",
                 closure, grpc_error_string(closure->error_data.error));
       }
-      call_combiner_sched_closure(call_combiner, closure,
-                                  closure->error_data.error);
+      ScheduleClosure(closure, closure->error_data.error);
       break;
     }
-  } else if (grpc_call_combiner_trace.enabled()) {
+  } else if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
     gpr_log(GPR_INFO, "  queue empty");
   }
 }
 
-void grpc_call_combiner_set_notify_on_cancel(grpc_call_combiner* call_combiner,
-                                             grpc_closure* closure) {
+void CallCombiner::SetNotifyOnCancel(grpc_closure* closure) {
   GRPC_STATS_INC_CALL_COMBINER_SET_NOTIFY_ON_CANCEL();
   while (true) {
     // Decode original state.
-    gpr_atm original_state = gpr_atm_acq_load(&call_combiner->cancel_state);
-    grpc_error* original_error = decode_cancel_state_error(original_state);
+    gpr_atm original_state = gpr_atm_acq_load(&cancel_state_);
+    grpc_error* original_error = DecodeCancelStateError(original_state);
     // If error is set, invoke the cancellation closure immediately.
     // Otherwise, store the new closure.
     if (original_error != GRPC_ERROR_NONE) {
-      if (grpc_call_combiner_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
         gpr_log(GPR_INFO,
                 "call_combiner=%p: scheduling notify_on_cancel callback=%p "
                 "for pre-existing cancellation",
-                call_combiner, closure);
+                this, closure);
       }
       GRPC_CLOSURE_SCHED(closure, GRPC_ERROR_REF(original_error));
       break;
     } else {
-      if (gpr_atm_full_cas(&call_combiner->cancel_state, original_state,
-                           (gpr_atm)closure)) {
-        if (grpc_call_combiner_trace.enabled()) {
+      if (gpr_atm_full_cas(&cancel_state_, original_state, (gpr_atm)closure)) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
           gpr_log(GPR_INFO, "call_combiner=%p: setting notify_on_cancel=%p",
-                  call_combiner, closure);
+                  this, closure);
         }
         // If we replaced an earlier closure, invoke the original
         // closure with GRPC_ERROR_NONE.  This allows callers to clean
         // up any resources they may be holding for the callback.
         if (original_state != 0) {
           closure = (grpc_closure*)original_state;
-          if (grpc_call_combiner_trace.enabled()) {
+          if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
             gpr_log(GPR_INFO,
-                    "call_combiner=%p: scheduling old cancel callback=%p",
-                    call_combiner, closure);
+                    "call_combiner=%p: scheduling old cancel callback=%p", this,
+                    closure);
           }
           GRPC_CLOSURE_SCHED(closure, GRPC_ERROR_NONE);
         }
@@ -234,24 +227,23 @@
   }
 }
 
-void grpc_call_combiner_cancel(grpc_call_combiner* call_combiner,
-                               grpc_error* error) {
+void CallCombiner::Cancel(grpc_error* error) {
   GRPC_STATS_INC_CALL_COMBINER_CANCELLED();
   while (true) {
-    gpr_atm original_state = gpr_atm_acq_load(&call_combiner->cancel_state);
-    grpc_error* original_error = decode_cancel_state_error(original_state);
+    gpr_atm original_state = gpr_atm_acq_load(&cancel_state_);
+    grpc_error* original_error = DecodeCancelStateError(original_state);
     if (original_error != GRPC_ERROR_NONE) {
       GRPC_ERROR_UNREF(error);
       break;
     }
-    if (gpr_atm_full_cas(&call_combiner->cancel_state, original_state,
-                         encode_cancel_state_error(error))) {
+    if (gpr_atm_full_cas(&cancel_state_, original_state,
+                         EncodeCancelStateError(error))) {
       if (original_state != 0) {
         grpc_closure* notify_on_cancel = (grpc_closure*)original_state;
-        if (grpc_call_combiner_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
           gpr_log(GPR_INFO,
                   "call_combiner=%p: scheduling notify_on_cancel callback=%p",
-                  call_combiner, notify_on_cancel);
+                  this, notify_on_cancel);
         }
         GRPC_CLOSURE_SCHED(notify_on_cancel, GRPC_ERROR_REF(error));
       }
@@ -260,3 +252,5 @@
     // cas failed, try again.
   }
 }
+
+}  // namespace grpc_core
diff --git a/src/core/lib/iomgr/call_combiner.h b/src/core/lib/iomgr/call_combiner.h
index 4ec0044..a10b437 100644
--- a/src/core/lib/iomgr/call_combiner.h
+++ b/src/core/lib/iomgr/call_combiner.h
@@ -41,15 +41,78 @@
 // when it is done with the action that was kicked off by the original
 // callback.
 
-extern grpc_core::TraceFlag grpc_call_combiner_trace;
+namespace grpc_core {
 
-struct grpc_call_combiner {
-  gpr_atm size = 0;  // size_t, num closures in queue or currently executing
-  gpr_mpscq queue;
+extern TraceFlag grpc_call_combiner_trace;
+
+class CallCombiner {
+ public:
+  CallCombiner();
+  ~CallCombiner();
+
+#ifndef NDEBUG
+#define GRPC_CALL_COMBINER_START(call_combiner, closure, error, reason) \
+  (call_combiner)->Start((closure), (error), __FILE__, __LINE__, (reason))
+#define GRPC_CALL_COMBINER_STOP(call_combiner, reason) \
+  (call_combiner)->Stop(__FILE__, __LINE__, (reason))
+  /// Starts processing \a closure.
+  void Start(grpc_closure* closure, grpc_error* error, const char* file,
+             int line, const char* reason);
+  /// Yields the call combiner to the next closure in the queue, if any.
+  void Stop(const char* file, int line, const char* reason);
+#else
+#define GRPC_CALL_COMBINER_START(call_combiner, closure, error, reason) \
+  (call_combiner)->Start((closure), (error), (reason))
+#define GRPC_CALL_COMBINER_STOP(call_combiner, reason) \
+  (call_combiner)->Stop((reason))
+  /// Starts processing \a closure.
+  void Start(grpc_closure* closure, grpc_error* error, const char* reason);
+  /// Yields the call combiner to the next closure in the queue, if any.
+  void Stop(const char* reason);
+#endif
+
+  /// Registers \a closure to be invoked when Cancel() is called.
+  ///
+  /// Once a closure is registered, it will always be scheduled exactly
+  /// once; this allows the closure to hold references that will be freed
+  /// regardless of whether or not the call was cancelled.  If a cancellation
+  /// does occur, the closure will be scheduled with the cancellation error;
+  /// otherwise, it will be scheduled with GRPC_ERROR_NONE.
+  ///
+  /// The closure will be scheduled in the following cases:
+  /// - If Cancel() was called prior to registering the closure, it will be
+  ///   scheduled immediately with the cancelation error.
+  /// - If Cancel() is called after registering the closure, the closure will
+  ///   be scheduled with the cancellation error.
+  /// - If SetNotifyOnCancel() is called again to register a new cancellation
+  ///   closure, the previous cancellation closure will be scheduled with
+  ///   GRPC_ERROR_NONE.
+  ///
+  /// If \a closure is NULL, then no closure will be invoked on
+  /// cancellation; this effectively unregisters the previously set closure.
+  /// However, most filters will not need to explicitly unregister their
+  /// callbacks, as this is done automatically when the call is destroyed.
+  /// Filters that schedule the cancellation closure on ExecCtx do not need
+  /// to take a ref on the call stack to guarantee closure liveness. This is
+  /// done by explicitly flushing ExecCtx after the unregistration during
+  /// call destruction.
+  void SetNotifyOnCancel(grpc_closure* closure);
+
+  /// Indicates that the call has been cancelled.
+  void Cancel(grpc_error* error);
+
+ private:
+  void ScheduleClosure(grpc_closure* closure, grpc_error* error);
+#ifdef GRPC_TSAN_ENABLED
+  static void TsanClosure(void* arg, grpc_error* error);
+#endif
+
+  gpr_atm size_ = 0;  // size_t, num closures in queue or currently executing
+  gpr_mpscq queue_;
   // Either 0 (if not cancelled and no cancellation closure set),
   // a grpc_closure* (if the lowest bit is 0),
   // or a grpc_error* (if the lowest bit is 1).
-  gpr_atm cancel_state = 0;
+  gpr_atm cancel_state_ = 0;
 #ifdef GRPC_TSAN_ENABLED
   // A fake ref-counted lock that is kept alive after the destruction of
   // grpc_call_combiner, when we are running the original closure.
@@ -58,90 +121,20 @@
   // callback is called. However, original_closure is free to trigger
   // anything on the call combiner (including destruction of grpc_call).
   // Thus, we need a ref-counted structure that can outlive the call combiner.
-  struct TsanLock
-      : public grpc_core::RefCounted<TsanLock,
-                                     grpc_core::NonPolymorphicRefCount> {
+  struct TsanLock : public RefCounted<TsanLock, NonPolymorphicRefCount> {
     TsanLock() { TSAN_ANNOTATE_RWLOCK_CREATE(&taken); }
     ~TsanLock() { TSAN_ANNOTATE_RWLOCK_DESTROY(&taken); }
-
     // To avoid double-locking by the same thread, we should acquire/release
     // the lock only when taken is false. On each acquire taken must be set to
     // true.
     std::atomic<bool> taken{false};
   };
-  grpc_core::RefCountedPtr<TsanLock> tsan_lock =
-      grpc_core::MakeRefCounted<TsanLock>();
-  grpc_closure tsan_closure;
-  grpc_closure* original_closure;
+  RefCountedPtr<TsanLock> tsan_lock_ = MakeRefCounted<TsanLock>();
+  grpc_closure tsan_closure_;
+  grpc_closure* original_closure_;
 #endif
 };
 
-// Assumes memory was initialized to zero.
-void grpc_call_combiner_init(grpc_call_combiner* call_combiner);
-
-void grpc_call_combiner_destroy(grpc_call_combiner* call_combiner);
-
-#ifndef NDEBUG
-#define GRPC_CALL_COMBINER_START(call_combiner, closure, error, reason)   \
-  grpc_call_combiner_start((call_combiner), (closure), (error), __FILE__, \
-                           __LINE__, (reason))
-#define GRPC_CALL_COMBINER_STOP(call_combiner, reason) \
-  grpc_call_combiner_stop((call_combiner), __FILE__, __LINE__, (reason))
-/// Starts processing \a closure on \a call_combiner.
-void grpc_call_combiner_start(grpc_call_combiner* call_combiner,
-                              grpc_closure* closure, grpc_error* error,
-                              const char* file, int line, const char* reason);
-/// Yields the call combiner to the next closure in the queue, if any.
-void grpc_call_combiner_stop(grpc_call_combiner* call_combiner,
-                             const char* file, int line, const char* reason);
-#else
-#define GRPC_CALL_COMBINER_START(call_combiner, closure, error, reason) \
-  grpc_call_combiner_start((call_combiner), (closure), (error), (reason))
-#define GRPC_CALL_COMBINER_STOP(call_combiner, reason) \
-  grpc_call_combiner_stop((call_combiner), (reason))
-/// Starts processing \a closure on \a call_combiner.
-void grpc_call_combiner_start(grpc_call_combiner* call_combiner,
-                              grpc_closure* closure, grpc_error* error,
-                              const char* reason);
-/// Yields the call combiner to the next closure in the queue, if any.
-void grpc_call_combiner_stop(grpc_call_combiner* call_combiner,
-                             const char* reason);
-#endif
-
-/// Registers \a closure to be invoked by \a call_combiner when
-/// grpc_call_combiner_cancel() is called.
-///
-/// Once a closure is registered, it will always be scheduled exactly
-/// once; this allows the closure to hold references that will be freed
-/// regardless of whether or not the call was cancelled.  If a cancellation
-/// does occur, the closure will be scheduled with the cancellation error;
-/// otherwise, it will be scheduled with GRPC_ERROR_NONE.
-///
-/// The closure will be scheduled in the following cases:
-/// - If grpc_call_combiner_cancel() was called prior to registering the
-///   closure, it will be scheduled immediately with the cancelation error.
-/// - If grpc_call_combiner_cancel() is called after registering the
-///   closure, the closure will be scheduled with the cancellation error.
-/// - If grpc_call_combiner_set_notify_on_cancel() is called again to
-///   register a new cancellation closure, the previous cancellation
-///   closure will be scheduled with GRPC_ERROR_NONE.
-///
-/// If \a closure is NULL, then no closure will be invoked on
-/// cancellation; this effectively unregisters the previously set closure.
-/// However, most filters will not need to explicitly unregister their
-/// callbacks, as this is done automatically when the call is destroyed. Filters
-/// that schedule the cancellation closure on ExecCtx do not need to take a ref
-/// on the call stack to guarantee closure liveness. This is done by explicitly
-/// flushing ExecCtx after the unregistration during call destruction.
-void grpc_call_combiner_set_notify_on_cancel(grpc_call_combiner* call_combiner,
-                                             grpc_closure* closure);
-
-/// Indicates that the call has been cancelled.
-void grpc_call_combiner_cancel(grpc_call_combiner* call_combiner,
-                               grpc_error* error);
-
-namespace grpc_core {
-
 // Helper for running a list of closures in a call combiner.
 //
 // Each callback running in the call combiner will eventually be
@@ -166,7 +159,7 @@
   // scheduled via GRPC_CLOSURE_SCHED(), which will eventually result in
   // yielding the call combiner.  If the list is empty, then the call
   // combiner will be yielded immediately.
-  void RunClosures(grpc_call_combiner* call_combiner) {
+  void RunClosures(CallCombiner* call_combiner) {
     if (closures_.empty()) {
       GRPC_CALL_COMBINER_STOP(call_combiner, "no closures to schedule");
       return;
@@ -176,7 +169,7 @@
       GRPC_CALL_COMBINER_START(call_combiner, closure.closure, closure.error,
                                closure.reason);
     }
-    if (grpc_call_combiner_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_call_combiner_trace)) {
       gpr_log(GPR_INFO,
               "CallCombinerClosureList executing closure while already "
               "holding call_combiner %p: closure=%p error=%s reason=%s",
@@ -190,7 +183,7 @@
 
   // Runs all closures in the call combiner, but does NOT yield the call
   // combiner.  All closures will be scheduled via GRPC_CALL_COMBINER_START().
-  void RunClosuresWithoutYielding(grpc_call_combiner* call_combiner) {
+  void RunClosuresWithoutYielding(CallCombiner* call_combiner) {
     for (size_t i = 0; i < closures_.size(); ++i) {
       auto& closure = closures_[i];
       GRPC_CALL_COMBINER_START(call_combiner, closure.closure, closure.error,
diff --git a/src/core/lib/iomgr/cfstream_handle.h b/src/core/lib/iomgr/cfstream_handle.h
index 93ec5f0..4f4d159 100644
--- a/src/core/lib/iomgr/cfstream_handle.h
+++ b/src/core/lib/iomgr/cfstream_handle.h
@@ -37,8 +37,8 @@
   static CFStreamHandle* CreateStreamHandle(CFReadStreamRef read_stream,
                                             CFWriteStreamRef write_stream);
   ~CFStreamHandle();
-  CFStreamHandle(const CFReadStreamRef& ref) = delete;
-  CFStreamHandle(CFReadStreamRef&& ref) = delete;
+  CFStreamHandle(const CFStreamHandle& ref) = delete;
+  CFStreamHandle(CFStreamHandle&& ref) = delete;
   CFStreamHandle& operator=(const CFStreamHandle& rhs) = delete;
 
   void NotifyOnOpen(grpc_closure* closure);
diff --git a/src/core/lib/iomgr/combiner.h b/src/core/lib/iomgr/combiner.h
index 3c947bf..b927421 100644
--- a/src/core/lib/iomgr/combiner.h
+++ b/src/core/lib/iomgr/combiner.h
@@ -31,7 +31,7 @@
 // Provides serialized access to some resource.
 // Each action queued on a combiner is executed serially in a borrowed thread.
 // The actual thread executing actions may change over time (but there will only
-// every be one at a time).
+// ever be one at a time).
 
 // Initialize the lock, with an optional workqueue to shift load to when
 // necessary
diff --git a/src/core/lib/iomgr/endpoint_pair_windows.cc b/src/core/lib/iomgr/endpoint_pair_windows.cc
index 177331d..9962809 100644
--- a/src/core/lib/iomgr/endpoint_pair_windows.cc
+++ b/src/core/lib/iomgr/endpoint_pair_windows.cc
@@ -41,7 +41,7 @@
   int addr_len = sizeof(addr);
 
   lst_sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
-                       WSA_FLAG_OVERLAPPED);
+                       grpc_get_default_wsa_socket_flags());
   GPR_ASSERT(lst_sock != INVALID_SOCKET);
 
   memset(&addr, 0, sizeof(addr));
@@ -54,7 +54,7 @@
              SOCKET_ERROR);
 
   cli_sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
-                       WSA_FLAG_OVERLAPPED);
+                       grpc_get_default_wsa_socket_flags());
   GPR_ASSERT(cli_sock != INVALID_SOCKET);
 
   GPR_ASSERT(WSAConnect(cli_sock, (grpc_sockaddr*)&addr, addr_len, NULL, NULL,
diff --git a/src/core/lib/iomgr/error.h b/src/core/lib/iomgr/error.h
index fcc6f07..7b04888 100644
--- a/src/core/lib/iomgr/error.h
+++ b/src/core/lib/iomgr/error.h
@@ -30,6 +30,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/debug/trace.h"
+#include "src/core/lib/gprpp/inlined_vector.h"
 
 /// Opaque representation of an error.
 /// See https://github.com/grpc/grpc/blob/master/doc/core/grpc-error.md for a
@@ -165,6 +166,9 @@
   grpc_error_create(__FILE__, __LINE__, grpc_slice_from_copied_string(desc), \
                     errs, count)
 
+#define GRPC_ERROR_CREATE_FROM_VECTOR(desc, error_list) \
+  grpc_error_create_from_vector(__FILE__, __LINE__, desc, error_list)
+
 #ifndef NDEBUG
 grpc_error* grpc_error_do_ref(grpc_error* err, const char* file, int line);
 void grpc_error_do_unref(grpc_error* err, const char* file, int line);
@@ -193,6 +197,25 @@
 #define GRPC_ERROR_UNREF(err) grpc_error_unref(err)
 #endif
 
+// Consumes all the errors in the vector and forms a referencing error from
+// them. If the vector is empty, return GRPC_ERROR_NONE.
+template <size_t N>
+static grpc_error* grpc_error_create_from_vector(
+    const char* file, int line, const char* desc,
+    grpc_core::InlinedVector<grpc_error*, N>* error_list) {
+  grpc_error* error = GRPC_ERROR_NONE;
+  if (error_list->size() != 0) {
+    error = grpc_error_create(file, line, grpc_slice_from_static_string(desc),
+                              error_list->data(), error_list->size());
+    // Remove refs to all errors in error_list.
+    for (size_t i = 0; i < error_list->size(); i++) {
+      GRPC_ERROR_UNREF((*error_list)[i]);
+    }
+    error_list->clear();
+  }
+  return error;
+}
+
 grpc_error* grpc_error_set_int(grpc_error* src, grpc_error_ints which,
                                intptr_t value) GRPC_MUST_USE_RESULT;
 /// It is an error to pass nullptr as `p`. Caller should allocate a dummy
diff --git a/src/core/lib/iomgr/ev_epoll1_linux.cc b/src/core/lib/iomgr/ev_epoll1_linux.cc
index b6f804c..cacf7f9 100644
--- a/src/core/lib/iomgr/ev_epoll1_linux.cc
+++ b/src/core/lib/iomgr/ev_epoll1_linux.cc
@@ -213,7 +213,7 @@
      poll */
   bool seen_inactive;
   bool shutting_down;             /* Is the pollset shutting down ? */
-  grpc_closure* shutdown_closure; /* Called after after shutdown is complete */
+  grpc_closure* shutdown_closure; /* Called after shutdown is complete */
 
   /* Number of workers who are *about-to* attach themselves to the pollset
    * worker list */
@@ -351,7 +351,7 @@
   grpc_iomgr_register_object(&new_fd->iomgr_object, fd_name);
   fork_fd_list_add_grpc_fd(new_fd);
 #ifndef NDEBUG
-  if (grpc_trace_fd_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_fd_refcount)) {
     gpr_log(GPR_DEBUG, "FD %d %p create %s", fd, new_fd, fd_name);
   }
 #endif
@@ -383,6 +383,13 @@
   if (fd->read_closure->SetShutdown(GRPC_ERROR_REF(why))) {
     if (!releasing_fd) {
       shutdown(fd->fd, SHUT_RDWR);
+    } else {
+      /* we need a dummy event for earlier linux versions. */
+      epoll_event dummy_event;
+      if (epoll_ctl(g_epoll_set.epfd, EPOLL_CTL_DEL, fd->fd, &dummy_event) !=
+          0) {
+        gpr_log(GPR_ERROR, "epoll_ctl failed: %s", strerror(errno));
+      }
     }
     fd->write_closure->SetShutdown(GRPC_ERROR_REF(why));
     fd->error_closure->SetShutdown(GRPC_ERROR_REF(why));
@@ -724,7 +731,7 @@
 
   GRPC_STATS_INC_POLL_EVENTS_RETURNED(r);
 
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "ps: %p poll got %d events", ps, r);
   }
 
@@ -744,7 +751,7 @@
   worker->schedule_on_end_work = (grpc_closure_list)GRPC_CLOSURE_LIST_INIT;
   pollset->begin_refs++;
 
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PS:%p BEGIN_STARTS:%p", pollset, worker);
   }
 
@@ -763,7 +770,7 @@
   retry_lock_neighborhood:
     gpr_mu_lock(&neighborhood->mu);
     gpr_mu_lock(&pollset->mu);
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, "PS:%p BEGIN_REORG:%p kick_state=%s is_reassigning=%d",
               pollset, worker, kick_state_string(worker->state),
               is_reassigning);
@@ -815,7 +822,7 @@
     worker->initialized_cv = true;
     gpr_cv_init(&worker->cv);
     while (worker->state == UNKICKED && !pollset->shutting_down) {
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO, "PS:%p BEGIN_WAIT:%p kick_state=%s shutdown=%d",
                 pollset, worker, kick_state_string(worker->state),
                 pollset->shutting_down);
@@ -832,7 +839,7 @@
     grpc_core::ExecCtx::Get()->InvalidateNow();
   }
 
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO,
             "PS:%p BEGIN_DONE:%p kick_state=%s shutdown=%d "
             "kicked_without_poller: %d",
@@ -875,7 +882,7 @@
           case UNKICKED:
             if (gpr_atm_no_barrier_cas(&g_active_poller, 0,
                                        (gpr_atm)inspect_worker)) {
-              if (grpc_polling_trace.enabled()) {
+              if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
                 gpr_log(GPR_INFO, " .. choose next poller to be %p",
                         inspect_worker);
               }
@@ -886,7 +893,7 @@
                 gpr_cv_signal(&inspect_worker->cv);
               }
             } else {
-              if (grpc_polling_trace.enabled()) {
+              if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
                 gpr_log(GPR_INFO, " .. beaten to choose next poller");
               }
             }
@@ -904,7 +911,7 @@
       } while (!found_worker && inspect_worker != inspect->root_worker);
     }
     if (!found_worker) {
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO, " .. mark pollset %p inactive", inspect);
       }
       inspect->seen_inactive = true;
@@ -924,7 +931,7 @@
 static void end_worker(grpc_pollset* pollset, grpc_pollset_worker* worker,
                        grpc_pollset_worker** worker_hdl) {
   GPR_TIMER_SCOPE("end_worker", 0);
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PS:%p END_WORKER:%p", pollset, worker);
   }
   if (worker_hdl != nullptr) *worker_hdl = nullptr;
@@ -934,7 +941,7 @@
                          grpc_core::ExecCtx::Get()->closure_list());
   if (gpr_atm_no_barrier_load(&g_active_poller) == (gpr_atm)worker) {
     if (worker->next != worker && worker->next->state == UNKICKED) {
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO, " .. choose next poller to be peer %p", worker);
       }
       GPR_ASSERT(worker->next->initialized_cv);
@@ -986,7 +993,7 @@
   if (worker->initialized_cv) {
     gpr_cv_destroy(&worker->cv);
   }
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, " .. remove worker");
   }
   if (EMPTIED == worker_remove(pollset, worker)) {
@@ -1055,7 +1062,7 @@
   GPR_TIMER_SCOPE("pollset_kick", 0);
   GRPC_STATS_INC_POLLSET_KICK();
   grpc_error* ret_err = GRPC_ERROR_NONE;
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_strvec log;
     gpr_strvec_init(&log);
     char* tmp;
@@ -1088,7 +1095,7 @@
       if (root_worker == nullptr) {
         GRPC_STATS_INC_POLLSET_KICKED_WITHOUT_POLLER();
         pollset->kicked_without_poller = true;
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, " .. kicked_without_poller");
         }
         goto done;
@@ -1096,14 +1103,14 @@
       grpc_pollset_worker* next_worker = root_worker->next;
       if (root_worker->state == KICKED) {
         GRPC_STATS_INC_POLLSET_KICKED_AGAIN();
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, " .. already kicked %p", root_worker);
         }
         SET_KICK_STATE(root_worker, KICKED);
         goto done;
       } else if (next_worker->state == KICKED) {
         GRPC_STATS_INC_POLLSET_KICKED_AGAIN();
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, " .. already kicked %p", next_worker);
         }
         SET_KICK_STATE(next_worker, KICKED);
@@ -1114,7 +1121,7 @@
                  root_worker == (grpc_pollset_worker*)gpr_atm_no_barrier_load(
                                     &g_active_poller)) {
         GRPC_STATS_INC_POLLSET_KICK_WAKEUP_FD();
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, " .. kicked %p", root_worker);
         }
         SET_KICK_STATE(root_worker, KICKED);
@@ -1122,7 +1129,7 @@
         goto done;
       } else if (next_worker->state == UNKICKED) {
         GRPC_STATS_INC_POLLSET_KICK_WAKEUP_CV();
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, " .. kicked %p", next_worker);
         }
         GPR_ASSERT(next_worker->initialized_cv);
@@ -1131,7 +1138,7 @@
         goto done;
       } else if (next_worker->state == DESIGNATED_POLLER) {
         if (root_worker->state != DESIGNATED_POLLER) {
-          if (grpc_polling_trace.enabled()) {
+          if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
             gpr_log(
                 GPR_INFO,
                 " .. kicked root non-poller %p (initialized_cv=%d) (poller=%p)",
@@ -1145,7 +1152,7 @@
           goto done;
         } else {
           GRPC_STATS_INC_POLLSET_KICK_WAKEUP_FD();
-          if (grpc_polling_trace.enabled()) {
+          if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
             gpr_log(GPR_INFO, " .. non-root poller %p (root=%p)", next_worker,
                     root_worker);
           }
@@ -1161,7 +1168,7 @@
       }
     } else {
       GRPC_STATS_INC_POLLSET_KICK_OWN_THREAD();
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO, " .. kicked while waking up");
       }
       goto done;
@@ -1171,14 +1178,14 @@
   }
 
   if (specific_worker->state == KICKED) {
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, " .. specific worker already kicked");
     }
     goto done;
   } else if (gpr_tls_get(&g_current_thread_worker) ==
              (intptr_t)specific_worker) {
     GRPC_STATS_INC_POLLSET_KICK_OWN_THREAD();
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, " .. mark %p kicked", specific_worker);
     }
     SET_KICK_STATE(specific_worker, KICKED);
@@ -1186,7 +1193,7 @@
   } else if (specific_worker ==
              (grpc_pollset_worker*)gpr_atm_no_barrier_load(&g_active_poller)) {
     GRPC_STATS_INC_POLLSET_KICK_WAKEUP_FD();
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, " .. kick active poller");
     }
     SET_KICK_STATE(specific_worker, KICKED);
@@ -1194,7 +1201,7 @@
     goto done;
   } else if (specific_worker->initialized_cv) {
     GRPC_STATS_INC_POLLSET_KICK_WAKEUP_CV();
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, " .. kick waiting worker");
     }
     SET_KICK_STATE(specific_worker, KICKED);
@@ -1202,7 +1209,7 @@
     goto done;
   } else {
     GRPC_STATS_INC_POLLSET_KICKED_AGAIN();
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, " .. kick non-waiting worker");
     }
     SET_KICK_STATE(specific_worker, KICKED);
diff --git a/src/core/lib/iomgr/ev_epollex_linux.cc b/src/core/lib/iomgr/ev_epollex_linux.cc
index 01be46c..08116b3 100644
--- a/src/core/lib/iomgr/ev_epollex_linux.cc
+++ b/src/core/lib/iomgr/ev_epollex_linux.cc
@@ -47,7 +47,7 @@
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/block_annotate.h"
 #include "src/core/lib/iomgr/iomgr_internal.h"
 #include "src/core/lib/iomgr/is_epollexclusive_available.h"
@@ -164,7 +164,7 @@
     gpr_asprintf(&fd_name, "%s fd=%d", name, fd);
     grpc_iomgr_register_object(&iomgr_object, fd_name);
 #ifndef NDEBUG
-    if (grpc_trace_fd_refcount.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_fd_refcount)) {
       gpr_log(GPR_DEBUG, "FD %d %p create %s", fd, this, fd_name);
     }
 #endif
@@ -335,7 +335,7 @@
 #define UNREF_BY(fd, n, reason) unref_by(fd, n, reason, __FILE__, __LINE__)
 static void ref_by(grpc_fd* fd, int n, const char* reason, const char* file,
                    int line) {
-  if (grpc_trace_fd_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_fd_refcount)) {
     gpr_log(GPR_DEBUG,
             "FD %d %p   ref %d %" PRIdPTR " -> %" PRIdPTR " [%s; %s:%d]",
             fd->fd, fd, n, gpr_atm_no_barrier_load(&fd->refst),
@@ -364,7 +364,7 @@
 #ifndef NDEBUG
 static void unref_by(grpc_fd* fd, int n, const char* reason, const char* file,
                      int line) {
-  if (grpc_trace_fd_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_fd_refcount)) {
     gpr_log(GPR_DEBUG,
             "FD %d %p unref %d %" PRIdPTR " -> %" PRIdPTR " [%s; %s:%d]",
             fd->fd, fd, n, gpr_atm_no_barrier_load(&fd->refst),
@@ -586,7 +586,7 @@
 static pollable* pollable_ref(pollable* p) {
 #else
 static pollable* pollable_ref(pollable* p, int line, const char* reason) {
-  if (grpc_trace_pollable_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_pollable_refcount)) {
     int r = static_cast<int> gpr_atm_no_barrier_load(&p->refs.count);
     gpr_log(__FILE__, line, GPR_LOG_SEVERITY_DEBUG,
             "POLLABLE:%p   ref %d->%d %s", p, r, r + 1, reason);
@@ -601,7 +601,7 @@
 #else
 static void pollable_unref(pollable* p, int line, const char* reason) {
   if (p == nullptr) return;
-  if (grpc_trace_pollable_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_pollable_refcount)) {
     int r = static_cast<int> gpr_atm_no_barrier_load(&p->refs.count);
     gpr_log(__FILE__, line, GPR_LOG_SEVERITY_DEBUG,
             "POLLABLE:%p unref %d->%d %s", p, r, r - 1, reason);
@@ -621,7 +621,7 @@
   grpc_error* error = GRPC_ERROR_NONE;
   static const char* err_desc = "pollable_add_fd";
   const int epfd = p->epfd;
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "add fd %p (%d) to pollable %p", fd, fd->fd, p);
   }
 
@@ -669,7 +669,7 @@
 
 /* pollset->mu must be held while calling this function */
 static void pollset_maybe_finish_shutdown(grpc_pollset* pollset) {
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO,
             "PS:%p (pollable:%p) maybe_finish_shutdown sc=%p (target:!NULL) "
             "rw=%p (target:NULL) cpsc=%d (target:0)",
@@ -694,14 +694,14 @@
   grpc_core::MutexLock lock(&p->mu);
   GPR_ASSERT(specific_worker != nullptr);
   if (specific_worker->kicked) {
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, "PS:%p kicked_specific_but_already_kicked", p);
     }
     GRPC_STATS_INC_POLLSET_KICKED_AGAIN();
     return GRPC_ERROR_NONE;
   }
   if (gpr_tls_get(&g_current_thread_worker) == (intptr_t)specific_worker) {
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, "PS:%p kicked_specific_but_awake", p);
     }
     GRPC_STATS_INC_POLLSET_KICK_OWN_THREAD();
@@ -710,7 +710,7 @@
   }
   if (specific_worker == p->root_worker) {
     GRPC_STATS_INC_POLLSET_KICK_WAKEUP_FD();
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, "PS:%p kicked_specific_via_wakeup_fd", p);
     }
     specific_worker->kicked = true;
@@ -719,7 +719,7 @@
   }
   if (specific_worker->initialized_cv) {
     GRPC_STATS_INC_POLLSET_KICK_WAKEUP_CV();
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_INFO, "PS:%p kicked_specific_via_cv", p);
     }
     specific_worker->kicked = true;
@@ -735,7 +735,7 @@
                                 grpc_pollset_worker* specific_worker) {
   GPR_TIMER_SCOPE("pollset_kick", 0);
   GRPC_STATS_INC_POLLSET_KICK();
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO,
             "PS:%p kick %p tls_pollset=%p tls_worker=%p pollset.root_worker=%p",
             pollset, specific_worker,
@@ -745,7 +745,7 @@
   if (specific_worker == nullptr) {
     if (gpr_tls_get(&g_current_thread_pollset) != (intptr_t)pollset) {
       if (pollset->root_worker == nullptr) {
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, "PS:%p kicked_any_without_poller", pollset);
         }
         GRPC_STATS_INC_POLLSET_KICKED_WITHOUT_POLLER();
@@ -771,7 +771,7 @@
             pollset->root_worker->links[PWLINK_POLLSET].next);
       }
     } else {
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO, "PS:%p kicked_any_but_awake", pollset);
       }
       GRPC_STATS_INC_POLLSET_KICK_OWN_THREAD();
@@ -891,7 +891,7 @@
     struct epoll_event* ev = &pollable_obj->events[n];
     void* data_ptr = ev->data.ptr;
     if (1 & (intptr_t)data_ptr) {
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO, "PS:%p got pollset_wakeup %p", pollset, data_ptr);
       }
       append_error(&error,
@@ -909,7 +909,7 @@
       bool write_ev = (ev->events & EPOLLOUT) != 0;
       bool err_fallback = error && !track_err;
 
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO,
                 "PS:%p got fd %p: cancel=%d read=%d "
                 "write=%d",
@@ -941,7 +941,7 @@
   GPR_TIMER_SCOPE("pollable_epoll", 0);
   int timeout = poll_deadline_to_millis_timeout(deadline);
 
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     char* desc = pollable_desc(p);
     gpr_log(GPR_INFO, "POLLABLE:%p[%s] poll for %dms", p, desc, timeout);
     gpr_free(desc);
@@ -961,7 +961,7 @@
 
   if (r < 0) return GRPC_OS_ERROR(errno, "epoll_wait");
 
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "POLLABLE:%p got %d events", p, r);
   }
 
@@ -1031,7 +1031,7 @@
     worker->initialized_cv = true;
     gpr_cv_init(&worker->cv);
     gpr_mu_unlock(&pollset->mu);
-    if (grpc_polling_trace.enabled() &&
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace) &&
         worker->pollable_obj->root_worker != worker) {
       gpr_log(GPR_INFO, "PS:%p wait %p w=%p for %dms", pollset,
               worker->pollable_obj, worker,
@@ -1040,18 +1040,18 @@
     while (do_poll && worker->pollable_obj->root_worker != worker) {
       if (gpr_cv_wait(&worker->cv, &worker->pollable_obj->mu,
                       grpc_millis_to_timespec(deadline, GPR_CLOCK_REALTIME))) {
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, "PS:%p timeout_wait %p w=%p", pollset,
                   worker->pollable_obj, worker);
         }
         do_poll = false;
       } else if (worker->kicked) {
-        if (grpc_polling_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
           gpr_log(GPR_INFO, "PS:%p wakeup %p w=%p", pollset,
                   worker->pollable_obj, worker);
         }
         do_poll = false;
-      } else if (grpc_polling_trace.enabled() &&
+      } else if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace) &&
                  worker->pollable_obj->root_worker != worker) {
         gpr_log(GPR_INFO, "PS:%p spurious_wakeup %p w=%p", pollset,
                 worker->pollable_obj, worker);
@@ -1124,7 +1124,7 @@
 #ifndef NDEBUG
   WORKER_PTR->originator = gettid();
 #endif
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO,
             "PS:%p work hdl=%p worker=%p now=%" PRId64 " deadline=%" PRId64
             " kwp=%d pollable=%p",
@@ -1165,7 +1165,7 @@
     grpc_pollset* pollset, grpc_fd* fd) {
   static const char* err_desc = "pollset_transition_pollable_from_empty_to_fd";
   grpc_error* error = GRPC_ERROR_NONE;
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO,
             "PS:%p add fd %p (%d); transition pollable from empty to fd",
             pollset, fd, fd->fd);
@@ -1181,7 +1181,7 @@
     grpc_pollset* pollset, grpc_fd* and_add_fd) {
   static const char* err_desc = "pollset_transition_pollable_from_fd_to_multi";
   grpc_error* error = GRPC_ERROR_NONE;
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(
         GPR_INFO,
         "PS:%p add fd %p (%d); transition pollable from fd %p to multipoller",
@@ -1253,7 +1253,7 @@
       error = pollable_create(PO_MULTI, &pollset->active_pollable);
       /* Any workers currently polling on this pollset must now be woked up so
        * that they can pick up the new active_pollable */
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO,
                 "PS:%p active pollable transition from empty to multi",
                 pollset);
@@ -1357,7 +1357,7 @@
 
 static void pollset_set_add_fd(grpc_pollset_set* pss, grpc_fd* fd) {
   GPR_TIMER_SCOPE("pollset_set_add_fd", 0);
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PSS:%p: add fd %p (%d)", pss, fd, fd->fd);
   }
   grpc_error* error = GRPC_ERROR_NONE;
@@ -1381,7 +1381,7 @@
 
 static void pollset_set_del_fd(grpc_pollset_set* pss, grpc_fd* fd) {
   GPR_TIMER_SCOPE("pollset_set_del_fd", 0);
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PSS:%p: del fd %p", pss, fd);
   }
   pss = pss_lock_adam(pss);
@@ -1402,7 +1402,7 @@
 
 static void pollset_set_del_pollset(grpc_pollset_set* pss, grpc_pollset* ps) {
   GPR_TIMER_SCOPE("pollset_set_del_pollset", 0);
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PSS:%p: del pollset %p", pss, ps);
   }
   pss = pss_lock_adam(pss);
@@ -1454,7 +1454,7 @@
 
 static void pollset_set_add_pollset(grpc_pollset_set* pss, grpc_pollset* ps) {
   GPR_TIMER_SCOPE("pollset_set_add_pollset", 0);
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PSS:%p: add pollset %p", pss, ps);
   }
   grpc_error* error = GRPC_ERROR_NONE;
@@ -1491,7 +1491,7 @@
 static void pollset_set_add_pollset_set(grpc_pollset_set* a,
                                         grpc_pollset_set* b) {
   GPR_TIMER_SCOPE("pollset_set_add_pollset_set", 0);
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PSS: merge (%p, %p)", a, b);
   }
   grpc_error* error = GRPC_ERROR_NONE;
@@ -1525,7 +1525,7 @@
   if (b_size > a_size) {
     GPR_SWAP(grpc_pollset_set*, a, b);
   }
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_INFO, "PSS: parent %p to %p", b, a);
   }
   gpr_ref(&a->refs);
diff --git a/src/core/lib/iomgr/ev_poll_posix.cc b/src/core/lib/iomgr/ev_poll_posix.cc
index 0c95cb7..cee6dcb 100644
--- a/src/core/lib/iomgr/ev_poll_posix.cc
+++ b/src/core/lib/iomgr/ev_poll_posix.cc
@@ -316,7 +316,7 @@
 #define UNREF_BY(fd, n, reason) unref_by(fd, n, reason, __FILE__, __LINE__)
 static void ref_by(grpc_fd* fd, int n, const char* reason, const char* file,
                    int line) {
-  if (grpc_trace_fd_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_fd_refcount)) {
     gpr_log(GPR_DEBUG,
             "FD %d %p   ref %d %" PRIdPTR " -> %" PRIdPTR " [%s; %s:%d]",
             fd->fd, fd, n, gpr_atm_no_barrier_load(&fd->refst),
@@ -333,7 +333,7 @@
 #ifndef NDEBUG
 static void unref_by(grpc_fd* fd, int n, const char* reason, const char* file,
                      int line) {
-  if (grpc_trace_fd_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_fd_refcount)) {
     gpr_log(GPR_DEBUG,
             "FD %d %p unref %d %" PRIdPTR " -> %" PRIdPTR " [%s; %s:%d]",
             fd->fd, fd, n, gpr_atm_no_barrier_load(&fd->refst),
@@ -561,7 +561,7 @@
 }
 
 static void fd_notify_on_error(grpc_fd* fd, grpc_closure* closure) {
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_ERROR, "Polling engine does not support tracking errors.");
   }
   GRPC_CLOSURE_SCHED(closure, GRPC_ERROR_CANCELLED);
@@ -580,7 +580,7 @@
 }
 
 static void fd_set_error(grpc_fd* fd) {
-  if (grpc_polling_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
     gpr_log(GPR_ERROR, "Polling engine does not support tracking errors.");
   }
 }
@@ -1012,7 +1012,7 @@
       r = grpc_poll_function(pfds, pfd_count, timeout);
       GRPC_SCHEDULING_END_BLOCKING_REGION;
 
-      if (grpc_polling_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
         gpr_log(GPR_INFO, "%p poll=%d", pollset, r);
       }
 
@@ -1036,7 +1036,7 @@
         }
       } else {
         if (pfds[0].revents & POLLIN_CHECK) {
-          if (grpc_polling_trace.enabled()) {
+          if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
             gpr_log(GPR_INFO, "%p: got_wakeup", pollset);
           }
           work_combine_error(
@@ -1046,7 +1046,7 @@
           if (watchers[i].fd == nullptr) {
             fd_end_poll(&watchers[i], 0, 0);
           } else {
-            if (grpc_polling_trace.enabled()) {
+            if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
               gpr_log(GPR_INFO, "%p got_event: %d r:%d w:%d [%d]", pollset,
                       pfds[i].fd, (pfds[i].revents & POLLIN_CHECK) != 0,
                       (pfds[i].revents & POLLOUT_CHECK) != 0, pfds[i].revents);
diff --git a/src/core/lib/iomgr/ev_posix.cc b/src/core/lib/iomgr/ev_posix.cc
index 898686b..ddafb7b 100644
--- a/src/core/lib/iomgr/ev_posix.cc
+++ b/src/core/lib/iomgr/ev_posix.cc
@@ -31,13 +31,19 @@
 #include <grpc/support/string_util.h>
 
 #include "src/core/lib/debug/trace.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/iomgr/ev_epoll1_linux.h"
 #include "src/core/lib/iomgr/ev_epollex_linux.h"
 #include "src/core/lib/iomgr/ev_poll_posix.h"
 #include "src/core/lib/iomgr/internal_errqueue.h"
 
+GPR_GLOBAL_CONFIG_DEFINE_STRING(
+    grpc_poll_strategy, "all",
+    "Declares which polling engines to try when starting gRPC. "
+    "This is a comma-separated list of engines, which are tried in priority "
+    "order first -> last.")
+
 grpc_core::TraceFlag grpc_polling_trace(false,
                                         "polling"); /* Disabled by default */
 
@@ -46,16 +52,15 @@
 grpc_core::DebugOnlyTraceFlag grpc_trace_fd_refcount(false, "fd_refcount");
 grpc_core::DebugOnlyTraceFlag grpc_polling_api_trace(false, "polling_api");
 
-#ifndef NDEBUG
-
 // Polling API trace only enabled in debug builds
+#ifndef NDEBUG
 #define GRPC_POLLING_API_TRACE(format, ...)                  \
-  if (grpc_polling_api_trace.enabled()) {                    \
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_api_trace)) {     \
     gpr_log(GPR_INFO, "(polling-api) " format, __VA_ARGS__); \
   }
 #else
 #define GRPC_POLLING_API_TRACE(...)
-#endif
+#endif  // NDEBUG
 
 /** Default poll() function - a pointer so that it can be overridden by some
  *  tests */
@@ -66,7 +71,7 @@
   return poll(fds, nfds, timeout);
 }
 grpc_poll_function_type grpc_poll_function = aix_poll;
-#endif
+#endif  // GPR_AIX
 
 grpc_wakeup_fd grpc_global_wakeup_fd;
 
@@ -205,14 +210,11 @@
 const char* grpc_get_poll_strategy_name() { return g_poll_strategy_name; }
 
 void grpc_event_engine_init(void) {
-  char* s = gpr_getenv("GRPC_POLL_STRATEGY");
-  if (s == nullptr) {
-    s = gpr_strdup("all");
-  }
+  grpc_core::UniquePtr<char> value = GPR_GLOBAL_CONFIG_GET(grpc_poll_strategy);
 
   char** strings = nullptr;
   size_t nstrings = 0;
-  split(s, &strings, &nstrings);
+  split(value.get(), &strings, &nstrings);
 
   for (size_t i = 0; g_event_engine == nullptr && i < nstrings; i++) {
     try_engine(strings[i]);
@@ -224,10 +226,10 @@
   gpr_free(strings);
 
   if (g_event_engine == nullptr) {
-    gpr_log(GPR_ERROR, "No event engine could be initialized from %s", s);
+    gpr_log(GPR_ERROR, "No event engine could be initialized from %s",
+            value.get());
     abort();
   }
-  gpr_free(s);
 }
 
 void grpc_event_engine_shutdown(void) {
diff --git a/src/core/lib/iomgr/ev_posix.h b/src/core/lib/iomgr/ev_posix.h
index 699173f..30bb5e4 100644
--- a/src/core/lib/iomgr/ev_posix.h
+++ b/src/core/lib/iomgr/ev_posix.h
@@ -24,16 +24,19 @@
 #include <poll.h>
 
 #include "src/core/lib/debug/trace.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/pollset_set.h"
 #include "src/core/lib/iomgr/wakeup_fd_posix.h"
 
+GPR_GLOBAL_CONFIG_DECLARE_STRING(grpc_poll_strategy);
+
 extern grpc_core::TraceFlag grpc_fd_trace;      /* Disabled by default */
 extern grpc_core::TraceFlag grpc_polling_trace; /* Disabled by default */
 
 #define GRPC_FD_TRACE(format, ...)                        \
-  if (grpc_fd_trace.enabled()) {                          \
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_fd_trace)) {           \
     gpr_log(GPR_INFO, "(fd-trace) " format, __VA_ARGS__); \
   }
 
diff --git a/src/core/lib/iomgr/executor.cc b/src/core/lib/iomgr/executor.cc
index 47836ac..8adc090 100644
--- a/src/core/lib/iomgr/executor.cc
+++ b/src/core/lib/iomgr/executor.cc
@@ -36,15 +36,19 @@
 
 #define MAX_DEPTH 2
 
-#define EXECUTOR_TRACE(format, ...)                     \
-  if (executor_trace.enabled()) {                       \
-    gpr_log(GPR_INFO, "EXECUTOR " format, __VA_ARGS__); \
-  }
+#define EXECUTOR_TRACE(format, ...)                       \
+  do {                                                    \
+    if (GRPC_TRACE_FLAG_ENABLED(executor_trace)) {        \
+      gpr_log(GPR_INFO, "EXECUTOR " format, __VA_ARGS__); \
+    }                                                     \
+  } while (0)
 
-#define EXECUTOR_TRACE0(str)            \
-  if (executor_trace.enabled()) {       \
-    gpr_log(GPR_INFO, "EXECUTOR " str); \
-  }
+#define EXECUTOR_TRACE0(str)                       \
+  do {                                             \
+    if (GRPC_TRACE_FLAG_ENABLED(executor_trace)) { \
+      gpr_log(GPR_INFO, "EXECUTOR " str);          \
+    }                                              \
+  } while (0)
 
 namespace grpc_core {
 namespace {
diff --git a/src/core/lib/iomgr/fork_posix.cc b/src/core/lib/iomgr/fork_posix.cc
index 7f8fb7e..629b081 100644
--- a/src/core/lib/iomgr/fork_posix.cc
+++ b/src/core/lib/iomgr/fork_posix.cc
@@ -28,7 +28,6 @@
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gprpp/fork.h"
 #include "src/core/lib/gprpp/thd.h"
 #include "src/core/lib/iomgr/ev_posix.h"
diff --git a/src/core/lib/iomgr/internal_errqueue.cc b/src/core/lib/iomgr/internal_errqueue.cc
index 4e2bfe3..b68c66b 100644
--- a/src/core/lib/iomgr/internal_errqueue.cc
+++ b/src/core/lib/iomgr/internal_errqueue.cc
@@ -36,7 +36,7 @@
 bool kernel_supports_errqueue() { return errqueue_supported; }
 
 void grpc_errqueue_init() {
-/* Both-compile time and run-time linux kernel versions should be atleast 4.0.0
+/* Both-compile time and run-time linux kernel versions should be at least 4.0.0
  */
 #ifdef GRPC_LINUX_ERRQUEUE
   struct utsname buffer;
diff --git a/src/core/lib/iomgr/iomgr.cc b/src/core/lib/iomgr/iomgr.cc
index 0fbfcfc..b86aa6f 100644
--- a/src/core/lib/iomgr/iomgr.cc
+++ b/src/core/lib/iomgr/iomgr.cc
@@ -29,9 +29,9 @@
 #include <grpc/support/string_util.h>
 #include <grpc/support/sync.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/gprpp/thd.h"
 #include "src/core/lib/iomgr/buffer_list.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
@@ -41,6 +41,10 @@
 #include "src/core/lib/iomgr/timer.h"
 #include "src/core/lib/iomgr/timer_manager.h"
 
+GPR_GLOBAL_CONFIG_DEFINE_BOOL(grpc_abort_on_leaks, false,
+                              "A debugging aid to cause a call to abort() when "
+                              "gRPC objects are leaked past grpc_shutdown()");
+
 static gpr_mu g_mu;
 static gpr_cv g_rcv;
 static int g_shutdown;
@@ -186,8 +190,5 @@
 }
 
 bool grpc_iomgr_abort_on_leaks(void) {
-  char* env = gpr_getenv("GRPC_ABORT_ON_LEAKS");
-  bool should_we = gpr_is_true(env);
-  gpr_free(env);
-  return should_we;
+  return GPR_GLOBAL_CONFIG_GET(grpc_abort_on_leaks);
 }
diff --git a/src/core/lib/iomgr/iomgr_windows.cc b/src/core/lib/iomgr/iomgr_windows.cc
index 13b5f87..728d404 100644
--- a/src/core/lib/iomgr/iomgr_windows.cc
+++ b/src/core/lib/iomgr/iomgr_windows.cc
@@ -61,6 +61,7 @@
   winsock_init();
   grpc_iocp_init();
   grpc_pollset_global_init();
+  grpc_wsa_socket_flags_init();
 }
 
 static void iomgr_platform_flush(void) { grpc_iocp_flush(); }
diff --git a/src/core/lib/iomgr/lockfree_event.cc b/src/core/lib/iomgr/lockfree_event.cc
index 085fea4..f0c40b4 100644
--- a/src/core/lib/iomgr/lockfree_event.cc
+++ b/src/core/lib/iomgr/lockfree_event.cc
@@ -94,7 +94,7 @@
      * sure that the shutdown error has been initialized properly before us
      * referencing it. */
     gpr_atm curr = gpr_atm_acq_load(&state_);
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, "LockfreeEvent::NotifyOn: %p curr=%p closure=%p", this,
               (void*)curr, closure);
     }
@@ -160,7 +160,7 @@
 
   while (true) {
     gpr_atm curr = gpr_atm_no_barrier_load(&state_);
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, "LockfreeEvent::SetShutdown: %p curr=%p err=%s",
               &state_, (void*)curr, grpc_error_string(shutdown_err));
     }
@@ -209,7 +209,7 @@
   while (true) {
     gpr_atm curr = gpr_atm_no_barrier_load(&state_);
 
-    if (grpc_polling_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, "LockfreeEvent::SetReady: %p curr=%p", &state_,
               (void*)curr);
     }
diff --git a/src/core/lib/iomgr/port.h b/src/core/lib/iomgr/port.h
index ccb4c31..d387de5 100644
--- a/src/core/lib/iomgr/port.h
+++ b/src/core/lib/iomgr/port.h
@@ -88,6 +88,15 @@
 #ifdef LINUX_VERSION_CODE
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)
 #define GRPC_HAVE_TCP_USER_TIMEOUT
+#ifdef __GLIBC_PREREQ
+#if !(__GLIBC_PREREQ(2, 17))
+/*
+ * TCP_USER_TIMEOUT wasn't imported to glibc until 2.17. Use Linux system
+ * header instead.
+ */
+#define GRPC_LINUX_TCP_H 1
+#endif /* __GLIBC_PREREQ(2, 17) */
+#endif /* ifdef __GLIBC_PREREQ */
 #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) */
 #endif /* LINUX_VERSION_CODE */
 #ifndef __GLIBC__
diff --git a/src/core/lib/iomgr/resource_quota.cc b/src/core/lib/iomgr/resource_quota.cc
index 61c3660..dffac34 100644
--- a/src/core/lib/iomgr/resource_quota.cc
+++ b/src/core/lib/iomgr/resource_quota.cc
@@ -32,6 +32,7 @@
 
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/iomgr/combiner.h"
+#include "src/core/lib/slice/slice_internal.h"
 
 grpc_core::TraceFlag grpc_resource_quota_trace(false, "resource_quota");
 
@@ -316,7 +317,7 @@
   while ((resource_user = rulist_pop_head(resource_quota,
                                           GRPC_RULIST_AWAITING_ALLOCATION))) {
     gpr_mu_lock(&resource_user->mu);
-    if (grpc_resource_quota_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
       gpr_log(GPR_INFO,
               "RQ: check allocation for user %p shutdown=%" PRIdPTR
               " free_pool=%" PRId64,
@@ -342,14 +343,14 @@
       resource_user->free_pool = 0;
       resource_quota->free_pool -= amt;
       rq_update_estimate(resource_quota);
-      if (grpc_resource_quota_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
         gpr_log(GPR_INFO,
                 "RQ %s %s: grant alloc %" PRId64
                 " bytes; rq_free_pool -> %" PRId64,
                 resource_quota->name, resource_user->name, amt,
                 resource_quota->free_pool);
       }
-    } else if (grpc_resource_quota_trace.enabled() &&
+    } else if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace) &&
                resource_user->free_pool >= 0) {
       gpr_log(GPR_INFO, "RQ %s %s: discard already satisfied alloc request",
               resource_quota->name, resource_user->name);
@@ -381,7 +382,7 @@
       resource_user->free_pool = 0;
       resource_quota->free_pool += amt;
       rq_update_estimate(resource_quota);
-      if (grpc_resource_quota_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
         gpr_log(GPR_INFO,
                 "RQ %s %s: reclaim_from_per_user_free_pool %" PRId64
                 " bytes; rq_free_pool -> %" PRId64,
@@ -391,7 +392,7 @@
       gpr_mu_unlock(&resource_user->mu);
       return true;
     } else {
-      if (grpc_resource_quota_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
         gpr_log(GPR_INFO,
                 "RQ %s %s: failed to reclaim_from_per_user_free_pool; "
                 "free_pool = %" PRId64 "; rq_free_pool = %" PRId64,
@@ -411,7 +412,7 @@
                                  : GRPC_RULIST_RECLAIMER_BENIGN;
   grpc_resource_user* resource_user = rulist_pop_head(resource_quota, list);
   if (resource_user == nullptr) return false;
-  if (grpc_resource_quota_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
     gpr_log(GPR_INFO, "RQ %s %s: initiate %s reclamation", resource_quota->name,
             resource_user->name, destructive ? "destructive" : "benign");
   }
@@ -430,41 +431,43 @@
  * ru_slice: a slice implementation that is backed by a grpc_resource_user
  */
 
-typedef struct {
-  grpc_slice_refcount base;
-  gpr_refcount refs;
-  grpc_resource_user* resource_user;
-  size_t size;
-} ru_slice_refcount;
+namespace grpc_core {
 
-static void ru_slice_ref(void* p) {
-  ru_slice_refcount* rc = static_cast<ru_slice_refcount*>(p);
-  gpr_ref(&rc->refs);
-}
-
-static void ru_slice_unref(void* p) {
-  ru_slice_refcount* rc = static_cast<ru_slice_refcount*>(p);
-  if (gpr_unref(&rc->refs)) {
-    grpc_resource_user_free(rc->resource_user, rc->size);
+class RuSliceRefcount {
+ public:
+  static void Destroy(void* p) {
+    auto* rc = static_cast<RuSliceRefcount*>(p);
+    rc->~RuSliceRefcount();
     gpr_free(rc);
   }
-}
+  RuSliceRefcount(grpc_resource_user* resource_user, size_t size)
+      : base_(grpc_slice_refcount::Type::REGULAR, &refs_, Destroy, this,
+              &base_),
+        resource_user_(resource_user),
+        size_(size) {
+    // Nothing to do here.
+  }
+  ~RuSliceRefcount() { grpc_resource_user_free(resource_user_, size_); }
 
-static const grpc_slice_refcount_vtable ru_slice_vtable = {
-    ru_slice_ref, ru_slice_unref, grpc_slice_default_eq_impl,
-    grpc_slice_default_hash_impl};
+  grpc_slice_refcount* base_refcount() { return &base_; }
+
+ private:
+  grpc_slice_refcount base_;
+  RefCount refs_;
+  grpc_resource_user* resource_user_;
+  size_t size_;
+};
+
+}  // namespace grpc_core
 
 static grpc_slice ru_slice_create(grpc_resource_user* resource_user,
                                   size_t size) {
-  ru_slice_refcount* rc = static_cast<ru_slice_refcount*>(
-      gpr_malloc(sizeof(ru_slice_refcount) + size));
-  rc->base.vtable = &ru_slice_vtable;
-  rc->base.sub_refcount = &rc->base;
-  gpr_ref_init(&rc->refs, 1);
-  rc->resource_user = resource_user;
-  rc->size = size;
+  auto* rc = static_cast<grpc_core::RuSliceRefcount*>(
+      gpr_malloc(sizeof(grpc_core::RuSliceRefcount) + size));
+  new (rc) grpc_core::RuSliceRefcount(resource_user, size);
   grpc_slice slice;
-  slice.refcount = &rc->base;
+
+  slice.refcount = rc->base_refcount();
   slice.data.refcounted.bytes = reinterpret_cast<uint8_t*>(rc + 1);
   slice.data.refcounted.length = size;
   return slice;
@@ -540,7 +543,7 @@
 }
 
 static void ru_shutdown(void* ru, grpc_error* error) {
-  if (grpc_resource_quota_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
     gpr_log(GPR_INFO, "RU shutdown %p", ru);
   }
   grpc_resource_user* resource_user = static_cast<grpc_resource_user*>(ru);
@@ -882,7 +885,7 @@
                                        grpc_closure* optional_on_done) {
   ru_ref_by(resource_user, static_cast<gpr_atm>(size));
   resource_user->free_pool -= static_cast<int64_t>(size);
-  if (grpc_resource_quota_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
     gpr_log(GPR_INFO, "RQ %s %s: alloc %" PRIdPTR "; free_pool -> %" PRId64,
             resource_user->resource_quota->name, resource_user->name, size,
             resource_user->free_pool);
@@ -941,7 +944,7 @@
   GPR_ASSERT(prior >= static_cast<long>(size));
   bool was_zero_or_negative = resource_user->free_pool <= 0;
   resource_user->free_pool += static_cast<int64_t>(size);
-  if (grpc_resource_quota_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
     gpr_log(GPR_INFO, "RQ %s %s: free %" PRIdPTR "; free_pool -> %" PRId64,
             resource_user->resource_quota->name, resource_user->name, size,
             resource_user->free_pool);
@@ -967,7 +970,7 @@
 }
 
 void grpc_resource_user_finish_reclamation(grpc_resource_user* resource_user) {
-  if (grpc_resource_quota_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_resource_quota_trace)) {
     gpr_log(GPR_INFO, "RQ %s %s: reclamation complete",
             resource_user->resource_quota->name, resource_user->name);
   }
diff --git a/src/core/lib/iomgr/socket_utils_common_posix.cc b/src/core/lib/iomgr/socket_utils_common_posix.cc
index 4c337a0..2101651 100644
--- a/src/core/lib/iomgr/socket_utils_common_posix.cc
+++ b/src/core/lib/iomgr/socket_utils_common_posix.cc
@@ -30,7 +30,11 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <netinet/in.h>
+#ifdef GRPC_LINUX_TCP_H
+#include <linux/tcp.h>
+#else
 #include <netinet/tcp.h>
+#endif
 #include <stdio.h>
 #include <string.h>
 #include <sys/socket.h>
@@ -288,7 +292,7 @@
   }
   if (enable) {
     extern grpc_core::TraceFlag grpc_tcp_trace;
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "Enabling TCP_USER_TIMEOUT with a timeout of %d ms",
               timeout);
     }
@@ -311,7 +315,7 @@
   }
 #else
   extern grpc_core::TraceFlag grpc_tcp_trace;
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP_USER_TIMEOUT not supported for this platform");
   }
 #endif /* GRPC_HAVE_TCP_USER_TIMEOUT */
diff --git a/src/core/lib/iomgr/socket_windows.cc b/src/core/lib/iomgr/socket_windows.cc
index 999c664..c87cfa8 100644
--- a/src/core/lib/iomgr/socket_windows.cc
+++ b/src/core/lib/iomgr/socket_windows.cc
@@ -39,6 +39,8 @@
 #include "src/core/lib/iomgr/sockaddr_windows.h"
 #include "src/core/lib/iomgr/socket_windows.h"
 
+static DWORD s_wsa_socket_flags;
+
 grpc_winsocket* grpc_winsocket_create(SOCKET socket, const char* name) {
   char* final_name;
   grpc_winsocket* r = (grpc_winsocket*)gpr_malloc(sizeof(grpc_winsocket));
@@ -181,4 +183,21 @@
   return g_ipv6_loopback_available;
 }
 
+DWORD grpc_get_default_wsa_socket_flags() { return s_wsa_socket_flags; }
+
+void grpc_wsa_socket_flags_init() {
+  s_wsa_socket_flags = WSA_FLAG_OVERLAPPED;
+  /* WSA_FLAG_NO_HANDLE_INHERIT may be not supported on the older Windows
+     versions, see
+     https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
+     for details. */
+  SOCKET sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
+                          s_wsa_socket_flags | WSA_FLAG_NO_HANDLE_INHERIT);
+  if (sock != INVALID_SOCKET) {
+    /* Windows 7, Windows 2008 R2 with SP1 or later */
+    s_wsa_socket_flags |= WSA_FLAG_NO_HANDLE_INHERIT;
+    closesocket(sock);
+  }
+}
+
 #endif /* GRPC_WINSOCK_SOCKET */
diff --git a/src/core/lib/iomgr/socket_windows.h b/src/core/lib/iomgr/socket_windows.h
index 46d7d58..5fed690 100644
--- a/src/core/lib/iomgr/socket_windows.h
+++ b/src/core/lib/iomgr/socket_windows.h
@@ -32,6 +32,10 @@
 #include "src/core/lib/iomgr/closure.h"
 #include "src/core/lib/iomgr/iomgr_internal.h"
 
+#ifndef WSA_FLAG_NO_HANDLE_INHERIT
+#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
+#endif
+
 /* This holds the data for an outstanding read or write on a socket.
    The mutex to protect the concurrent access to that data is the one
    inside the winsocket wrapper. */
@@ -114,6 +118,10 @@
    The value is probed once, and cached for the life of the process. */
 int grpc_ipv6_loopback_available(void);
 
+void grpc_wsa_socket_flags_init();
+
+DWORD grpc_get_default_wsa_socket_flags();
+
 #endif
 
 #endif /* GRPC_CORE_LIB_IOMGR_SOCKET_WINDOWS_H */
diff --git a/src/core/lib/iomgr/tcp_client_custom.cc b/src/core/lib/iomgr/tcp_client_custom.cc
index 73344b1..14a8b78 100644
--- a/src/core/lib/iomgr/tcp_client_custom.cc
+++ b/src/core/lib/iomgr/tcp_client_custom.cc
@@ -64,7 +64,7 @@
   int done;
   grpc_custom_socket* socket = (grpc_custom_socket*)acp;
   grpc_custom_tcp_connect* connect = socket->connector;
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     const char* str = grpc_error_string(error);
     gpr_log(GPR_INFO, "CLIENT_CONNECT: %s: on_alarm: error=%s",
             connect->addr_name, str);
@@ -146,7 +146,7 @@
   socket->listener = nullptr;
   connect->refs = 2;
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "CLIENT_CONNECT: %p %s: asynchronously connecting",
             socket, connect->addr_name);
   }
diff --git a/src/core/lib/iomgr/tcp_client_posix.cc b/src/core/lib/iomgr/tcp_client_posix.cc
index 0bff74e..ad9d779 100644
--- a/src/core/lib/iomgr/tcp_client_posix.cc
+++ b/src/core/lib/iomgr/tcp_client_posix.cc
@@ -108,7 +108,7 @@
 static void tc_on_alarm(void* acp, grpc_error* error) {
   int done;
   async_connect* ac = static_cast<async_connect*>(acp);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     const char* str = grpc_error_string(error);
     gpr_log(GPR_INFO, "CLIENT_CONNECT: %s: on_alarm: error=%s", ac->addr_str,
             str);
@@ -145,7 +145,7 @@
 
   GRPC_ERROR_REF(error);
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     const char* str = grpc_error_string(error);
     gpr_log(GPR_INFO, "CLIENT_CONNECT: %s: on_writable: error=%s", ac->addr_str,
             str);
@@ -328,7 +328,7 @@
                     grpc_schedule_on_exec_ctx);
   ac->channel_args = grpc_channel_args_copy(channel_args);
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "CLIENT_CONNECT: %s: asynchronously connecting fd %p",
             ac->addr_str, fdobj);
   }
diff --git a/src/core/lib/iomgr/tcp_client_windows.cc b/src/core/lib/iomgr/tcp_client_windows.cc
index e24431b..6669953 100644
--- a/src/core/lib/iomgr/tcp_client_windows.cc
+++ b/src/core/lib/iomgr/tcp_client_windows.cc
@@ -148,7 +148,7 @@
   }
 
   sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
-                   WSA_FLAG_OVERLAPPED);
+                   grpc_get_default_wsa_socket_flags());
   if (sock == INVALID_SOCKET) {
     error = GRPC_WSA_ERROR(WSAGetLastError(), "WSASocket");
     goto failure;
diff --git a/src/core/lib/iomgr/tcp_custom.cc b/src/core/lib/iomgr/tcp_custom.cc
index f7ad120..66df508 100644
--- a/src/core/lib/iomgr/tcp_custom.cc
+++ b/src/core/lib/iomgr/tcp_custom.cc
@@ -88,7 +88,7 @@
 #define TCP_REF(tcp, reason) tcp_ref((tcp), (reason), __FILE__, __LINE__)
 static void tcp_unref(custom_tcp_endpoint* tcp, const char* reason,
                       const char* file, int line) {
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_atm val = gpr_atm_no_barrier_load(&tcp->refcount.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_ERROR,
             "TCP unref %p : %s %" PRIdPTR " -> %" PRIdPTR, tcp->socket, reason,
@@ -101,7 +101,7 @@
 
 static void tcp_ref(custom_tcp_endpoint* tcp, const char* reason,
                     const char* file, int line) {
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_atm val = gpr_atm_no_barrier_load(&tcp->refcount.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_ERROR,
             "TCP   ref %p : %s %" PRIdPTR " -> %" PRIdPTR, tcp->socket, reason,
@@ -123,7 +123,7 @@
 
 static void call_read_cb(custom_tcp_endpoint* tcp, grpc_error* error) {
   grpc_closure* cb = tcp->read_cb;
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p call_cb %p %p:%p", tcp->socket, cb, cb->cb,
             cb->cb_arg);
     size_t i;
@@ -169,7 +169,7 @@
 
 static void tcp_read_allocation_done(void* tcpp, grpc_error* error) {
   custom_tcp_endpoint* tcp = (custom_tcp_endpoint*)tcpp;
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p read_allocation_done: %s", tcp->socket,
             grpc_error_string(error));
   }
@@ -185,7 +185,7 @@
     grpc_slice_buffer_reset_and_unref_internal(tcp->read_slices);
     call_read_cb(tcp, GRPC_ERROR_REF(error));
   }
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     const char* str = grpc_error_string(error);
     gpr_log(GPR_INFO, "Initiating read on %p: error=%s", tcp->socket, str);
   }
@@ -211,7 +211,7 @@
   custom_tcp_endpoint* tcp = (custom_tcp_endpoint*)socket->endpoint;
   grpc_closure* cb = tcp->write_cb;
   tcp->write_cb = nullptr;
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     const char* str = grpc_error_string(error);
     gpr_log(GPR_INFO, "write complete on %p: error=%s", tcp->socket, str);
   }
@@ -224,7 +224,7 @@
   custom_tcp_endpoint* tcp = (custom_tcp_endpoint*)ep;
   GRPC_CUSTOM_IOMGR_ASSERT_SAME_THREAD();
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     size_t j;
 
     for (j = 0; j < write_slices->count; j++) {
@@ -280,7 +280,7 @@
 static void endpoint_shutdown(grpc_endpoint* ep, grpc_error* why) {
   custom_tcp_endpoint* tcp = (custom_tcp_endpoint*)ep;
   if (!tcp->shutting_down) {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       const char* str = grpc_error_string(why);
       gpr_log(GPR_INFO, "TCP %p shutdown why=%s", tcp->socket, str);
     }
@@ -345,7 +345,7 @@
       (custom_tcp_endpoint*)gpr_malloc(sizeof(custom_tcp_endpoint));
   grpc_core::ExecCtx exec_ctx;
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "Creating TCP endpoint %p", socket);
   }
   memset(tcp, 0, sizeof(custom_tcp_endpoint));
diff --git a/src/core/lib/iomgr/tcp_posix.cc b/src/core/lib/iomgr/tcp_posix.cc
index 30305a9..b9376b3 100644
--- a/src/core/lib/iomgr/tcp_posix.cc
+++ b/src/core/lib/iomgr/tcp_posix.cc
@@ -163,7 +163,7 @@
 
 static void done_poller(void* bp, grpc_error* error_ignored) {
   backup_poller* p = static_cast<backup_poller*>(bp);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "BACKUP_POLLER:%p destroy", p);
   }
   grpc_pollset_destroy(BACKUP_POLLER_POLLSET(p));
@@ -172,7 +172,7 @@
 
 static void run_poller(void* bp, grpc_error* error_ignored) {
   backup_poller* p = static_cast<backup_poller*>(bp);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "BACKUP_POLLER:%p run", p);
   }
   gpr_mu_lock(p->pollset_mu);
@@ -188,18 +188,18 @@
       gpr_atm_full_cas(&g_uncovered_notifications_pending, 1, 0)) {
     gpr_mu_lock(p->pollset_mu);
     bool cas_ok = gpr_atm_full_cas(&g_backup_poller, (gpr_atm)p, 0);
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "BACKUP_POLLER:%p done cas_ok=%d", p, cas_ok);
     }
     gpr_mu_unlock(p->pollset_mu);
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "BACKUP_POLLER:%p shutdown", p);
     }
     grpc_pollset_shutdown(BACKUP_POLLER_POLLSET(p),
                           GRPC_CLOSURE_INIT(&p->run_poller, done_poller, p,
                                             grpc_schedule_on_exec_ctx));
   } else {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "BACKUP_POLLER:%p reschedule", p);
     }
     GRPC_CLOSURE_SCHED(&p->run_poller, GRPC_ERROR_NONE);
@@ -210,7 +210,7 @@
   backup_poller* p = (backup_poller*)gpr_atm_acq_load(&g_backup_poller);
   gpr_atm old_count =
       gpr_atm_full_fetch_add(&g_uncovered_notifications_pending, -1);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "BACKUP_POLLER:%p uncover cnt %d->%d", p,
             static_cast<int>(old_count), static_cast<int>(old_count) - 1);
   }
@@ -228,7 +228,7 @@
   backup_poller* p;
   gpr_atm old_count =
       gpr_atm_no_barrier_fetch_add(&g_uncovered_notifications_pending, 2);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "BACKUP_POLLER: cover cnt %d->%d",
             static_cast<int>(old_count), 2 + static_cast<int>(old_count));
   }
@@ -236,7 +236,7 @@
     GRPC_STATS_INC_TCP_BACKUP_POLLERS_CREATED();
     p = static_cast<backup_poller*>(
         gpr_zalloc(sizeof(*p) + grpc_pollset_size()));
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "BACKUP_POLLER:%p create", p);
     }
     grpc_pollset_init(BACKUP_POLLER_POLLSET(p), &p->pollset_mu);
@@ -251,7 +251,7 @@
       // spin waiting for backup poller
     }
   }
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "BACKUP_POLLER:%p add %p", p, tcp);
   }
   grpc_pollset_add_fd(BACKUP_POLLER_POLLSET(p), tcp->em_fd);
@@ -261,32 +261,24 @@
 }
 
 static void notify_on_read(grpc_tcp* tcp) {
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p notify_on_read", tcp);
   }
   grpc_fd_notify_on_read(tcp->em_fd, &tcp->read_done_closure);
 }
 
 static void notify_on_write(grpc_tcp* tcp) {
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p notify_on_write", tcp);
   }
-  if (grpc_event_engine_run_in_background()) {
-    // If there is a polling engine always running in the background, there is
-    // no need to run the backup poller.
-    GRPC_CLOSURE_INIT(&tcp->write_done_closure, tcp_handle_write, tcp,
-                      grpc_schedule_on_exec_ctx);
-  } else {
+  if (!grpc_event_engine_run_in_background()) {
     cover_self(tcp);
-    GRPC_CLOSURE_INIT(&tcp->write_done_closure,
-                      tcp_drop_uncovered_then_handle_write, tcp,
-                      grpc_schedule_on_exec_ctx);
   }
   grpc_fd_notify_on_write(tcp->em_fd, &tcp->write_done_closure);
 }
 
 static void tcp_drop_uncovered_then_handle_write(void* arg, grpc_error* error) {
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p got_write: %s", arg, grpc_error_string(error));
   }
   drop_uncovered(static_cast<grpc_tcp*>(arg));
@@ -371,7 +363,7 @@
 #define TCP_REF(tcp, reason) tcp_ref((tcp), (reason), __FILE__, __LINE__)
 static void tcp_unref(grpc_tcp* tcp, const char* reason, const char* file,
                       int line) {
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_atm val = gpr_atm_no_barrier_load(&tcp->refcount.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
             "TCP unref %p : %s %" PRIdPTR " -> %" PRIdPTR, tcp, reason, val,
@@ -384,7 +376,7 @@
 
 static void tcp_ref(grpc_tcp* tcp, const char* reason, const char* file,
                     int line) {
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_atm val = gpr_atm_no_barrier_load(&tcp->refcount.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
             "TCP   ref %p : %s %" PRIdPTR " -> %" PRIdPTR, tcp, reason, val,
@@ -417,7 +409,7 @@
 static void call_read_cb(grpc_tcp* tcp, grpc_error* error) {
   grpc_closure* cb = tcp->read_cb;
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p call_cb %p %p:%p", tcp, cb, cb->cb, cb->cb_arg);
     size_t i;
     const char* str = grpc_error_string(error);
@@ -581,7 +573,7 @@
 
 static void tcp_read_allocation_done(void* tcpp, grpc_error* error) {
   grpc_tcp* tcp = static_cast<grpc_tcp*>(tcpp);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p read_allocation_done: %s", tcp,
             grpc_error_string(error));
   }
@@ -600,13 +592,13 @@
   /* Wait for allocation only when there is no buffer left. */
   if (tcp->incoming_buffer->length == 0 &&
       tcp->incoming_buffer->count < MAX_READ_IOVEC) {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "TCP:%p alloc_slices", tcp);
     }
     grpc_resource_user_alloc_slices(&tcp->slice_allocator, target_read_size, 1,
                                     tcp->incoming_buffer);
   } else {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "TCP:%p do_read", tcp);
     }
     tcp_do_read(tcp);
@@ -615,7 +607,7 @@
 
 static void tcp_handle_read(void* arg /* grpc_tcp */, grpc_error* error) {
   grpc_tcp* tcp = static_cast<grpc_tcp*>(arg);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p got_read: %s", tcp, grpc_error_string(error));
   }
 
@@ -694,7 +686,7 @@
     if (setsockopt(tcp->fd, SOL_SOCKET, SO_TIMESTAMPING,
                    static_cast<void*>(&opt), sizeof(opt)) != 0) {
       grpc_slice_buffer_reset_and_unref_internal(tcp->outgoing_buffer);
-      if (grpc_tcp_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
         gpr_log(GPR_ERROR, "Failed to set timestamping options on the socket.");
       }
       return false;
@@ -743,7 +735,7 @@
   auto next_cmsg = CMSG_NXTHDR(msg, cmsg);
   cmsghdr* opt_stats = nullptr;
   if (next_cmsg == nullptr) {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_ERROR, "Received timestamp without extended error");
     }
     return cmsg;
@@ -755,7 +747,7 @@
     opt_stats = next_cmsg;
     next_cmsg = CMSG_NXTHDR(msg, opt_stats);
     if (next_cmsg == nullptr) {
-      if (grpc_tcp_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
         gpr_log(GPR_ERROR, "Received timestamp without extended error");
       }
       return opt_stats;
@@ -765,7 +757,7 @@
   if (!(next_cmsg->cmsg_level == SOL_IP || next_cmsg->cmsg_level == SOL_IPV6) ||
       !(next_cmsg->cmsg_type == IP_RECVERR ||
         next_cmsg->cmsg_type == IPV6_RECVERR)) {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_ERROR, "Unexpected control message");
     }
     return cmsg;
@@ -847,7 +839,7 @@
           cmsg->cmsg_type != SCM_TIMESTAMPING) {
         /* Got a control message that is not a timestamp. Don't know how to
          * handle this. */
-        if (grpc_tcp_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
           gpr_log(GPR_INFO,
                   "unknown control message cmsg_level:%d cmsg_type:%d",
                   cmsg->cmsg_level, cmsg->cmsg_type);
@@ -865,7 +857,7 @@
 
 static void tcp_handle_error(void* arg /* grpc_tcp */, grpc_error* error) {
   grpc_tcp* tcp = static_cast<grpc_tcp*>(arg);
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "TCP:%p got_error: %s", tcp, grpc_error_string(error));
   }
 
@@ -884,8 +876,6 @@
    * ready. */
   grpc_fd_set_readable(tcp->em_fd);
   grpc_fd_set_writable(tcp->em_fd);
-  GRPC_CLOSURE_INIT(&tcp->error_closure, tcp_handle_error, tcp,
-                    grpc_schedule_on_exec_ctx);
   grpc_fd_notify_on_error(tcp->em_fd, &tcp->error_closure);
 }
 
@@ -1044,14 +1034,14 @@
   }
 
   if (!tcp_flush(tcp, &error)) {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "write: delayed");
     }
     notify_on_write(tcp);
   } else {
     cb = tcp->write_cb;
     tcp->write_cb = nullptr;
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       const char* str = grpc_error_string(error);
       gpr_log(GPR_INFO, "write: %s", str);
     }
@@ -1066,7 +1056,7 @@
   grpc_tcp* tcp = reinterpret_cast<grpc_tcp*>(ep);
   grpc_error* error = GRPC_ERROR_NONE;
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     size_t i;
 
     for (i = 0; i < buf->count; i++) {
@@ -1101,12 +1091,12 @@
   if (!tcp_flush(tcp, &error)) {
     TCP_REF(tcp, "write");
     tcp->write_cb = cb;
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "write: delayed");
     }
     notify_on_write(tcp);
   } else {
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       const char* str = grpc_error_string(error);
       gpr_log(GPR_INFO, "write: %s", str);
     }
@@ -1248,6 +1238,16 @@
   tcp->tb_head = nullptr;
   GRPC_CLOSURE_INIT(&tcp->read_done_closure, tcp_handle_read, tcp,
                     grpc_schedule_on_exec_ctx);
+  if (grpc_event_engine_run_in_background()) {
+    // If there is a polling engine always running in the background, there is
+    // no need to run the backup poller.
+    GRPC_CLOSURE_INIT(&tcp->write_done_closure, tcp_handle_write, tcp,
+                      grpc_schedule_on_exec_ctx);
+  } else {
+    GRPC_CLOSURE_INIT(&tcp->write_done_closure,
+                      tcp_drop_uncovered_then_handle_write, tcp,
+                      grpc_schedule_on_exec_ctx);
+  }
   /* Always assume there is something on the queue to read. */
   tcp->inq = 1;
 #ifdef GRPC_HAVE_TCP_INQ
diff --git a/src/core/lib/iomgr/tcp_server_custom.cc b/src/core/lib/iomgr/tcp_server_custom.cc
index 019b354..133847d 100644
--- a/src/core/lib/iomgr/tcp_server_custom.cc
+++ b/src/core/lib/iomgr/tcp_server_custom.cc
@@ -220,7 +220,7 @@
     GRPC_LOG_IF_ERROR("getpeername error", err);
     GRPC_ERROR_UNREF(err);
   }
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     if (peer_name_string) {
       gpr_log(GPR_INFO, "SERVER_CONNECT: %p accepted connection: %s",
               sp->server, peer_name_string);
@@ -372,7 +372,7 @@
     addr = &wildcard;
   }
 
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     char* port_string;
     grpc_sockaddr_to_string(&port_string, addr, 0);
     const char* str = grpc_error_string(error);
@@ -418,7 +418,7 @@
   (void)pollsets;
   (void)pollset_count;
   GRPC_CUSTOM_IOMGR_ASSERT_SAME_THREAD();
-  if (grpc_tcp_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
     gpr_log(GPR_INFO, "SERVER_START %p", server);
   }
   GPR_ASSERT(on_accept_cb);
diff --git a/src/core/lib/iomgr/tcp_server_posix.cc b/src/core/lib/iomgr/tcp_server_posix.cc
index 824db07..0133972 100644
--- a/src/core/lib/iomgr/tcp_server_posix.cc
+++ b/src/core/lib/iomgr/tcp_server_posix.cc
@@ -217,12 +217,25 @@
       }
     }
 
+    /* For UNIX sockets, the accept call might not fill up the member sun_path
+     * of sockaddr_un, so explicitly call getsockname to get it. */
+    if (grpc_is_unix_socket(&addr)) {
+      memset(&addr, 0, sizeof(addr));
+      addr.len = static_cast<socklen_t>(sizeof(struct sockaddr_storage));
+      if (getsockname(fd, reinterpret_cast<struct sockaddr*>(addr.addr),
+                      &(addr.len)) < 0) {
+        gpr_log(GPR_ERROR, "Failed getsockname: %s", strerror(errno));
+        close(fd);
+        goto error;
+      }
+    }
+
     grpc_set_socket_no_sigpipe_if_possible(fd);
 
     addr_str = grpc_sockaddr_to_uri(&addr);
     gpr_asprintf(&name, "tcp-server-connection:%s", addr_str);
 
-    if (grpc_tcp_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_tcp_trace)) {
       gpr_log(GPR_INFO, "SERVER_CONNECT: incoming connection: %s", addr_str);
     }
 
diff --git a/src/core/lib/iomgr/tcp_server_windows.cc b/src/core/lib/iomgr/tcp_server_windows.cc
index b01afdc..7ac4234 100644
--- a/src/core/lib/iomgr/tcp_server_windows.cc
+++ b/src/core/lib/iomgr/tcp_server_windows.cc
@@ -255,7 +255,7 @@
   }
 
   sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
-                   WSA_FLAG_OVERLAPPED);
+                   grpc_get_default_wsa_socket_flags());
   if (sock == INVALID_SOCKET) {
     error = GRPC_WSA_ERROR(WSAGetLastError(), "WSASocket");
     goto failure;
@@ -493,7 +493,7 @@
   }
 
   sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
-                   WSA_FLAG_OVERLAPPED);
+                   grpc_get_default_wsa_socket_flags());
   if (sock == INVALID_SOCKET) {
     error = GRPC_WSA_ERROR(WSAGetLastError(), "WSASocket");
     goto done;
diff --git a/src/core/lib/iomgr/tcp_windows.cc b/src/core/lib/iomgr/tcp_windows.cc
index 1fb0ef8..ae6b2b6 100644
--- a/src/core/lib/iomgr/tcp_windows.cc
+++ b/src/core/lib/iomgr/tcp_windows.cc
@@ -74,12 +74,26 @@
              : GRPC_WSA_ERROR(WSAGetLastError(), "setsockopt(IPV6_V6ONLY)");
 }
 
+static grpc_error* enable_socket_low_latency(SOCKET sock) {
+  int status;
+  BOOL param = TRUE;
+  status = ::setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+                        reinterpret_cast<char*>(&param), sizeof(param));
+  if (status == SOCKET_ERROR) {
+    status = WSAGetLastError();
+  }
+  return status == 0 ? GRPC_ERROR_NONE
+                     : GRPC_WSA_ERROR(status, "setsockopt(TCP_NODELAY)");
+}
+
 grpc_error* grpc_tcp_prepare_socket(SOCKET sock) {
   grpc_error* err;
   err = grpc_tcp_set_non_block(sock);
   if (err != GRPC_ERROR_NONE) return err;
   err = set_dualstack(sock);
   if (err != GRPC_ERROR_NONE) return err;
+  err = enable_socket_low_latency(sock);
+  if (err != GRPC_ERROR_NONE) return err;
   return GRPC_ERROR_NONE;
 }
 
diff --git a/src/core/lib/iomgr/timer_generic.cc b/src/core/lib/iomgr/timer_generic.cc
index 6a925ad..00c1878 100644
--- a/src/core/lib/iomgr/timer_generic.cc
+++ b/src/core/lib/iomgr/timer_generic.cc
@@ -361,7 +361,7 @@
   timer->hash_table_next = nullptr;
 #endif
 
-  if (grpc_timer_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_trace)) {
     gpr_log(GPR_INFO, "TIMER %p: SET %" PRId64 " now %" PRId64 " call %p[%p]",
             timer, deadline, grpc_core::ExecCtx::Get()->Now(), closure,
             closure->cb);
@@ -397,7 +397,7 @@
     timer->heap_index = INVALID_HEAP_INDEX;
     list_join(&shard->list, timer);
   }
-  if (grpc_timer_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_trace)) {
     gpr_log(GPR_INFO,
             "  .. add to shard %d with queue_deadline_cap=%" PRId64
             " => is_first_timer=%s",
@@ -419,7 +419,7 @@
      grpc_timer_check. */
   if (is_first_timer) {
     gpr_mu_lock(&g_shared_mutables.mu);
-    if (grpc_timer_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_trace)) {
       gpr_log(GPR_INFO, "  .. old shard min_deadline=%" PRId64,
               shard->min_deadline);
     }
@@ -463,7 +463,7 @@
 
   timer_shard* shard = &g_shards[GPR_HASH_POINTER(timer, g_num_shards)];
   gpr_mu_lock(&shard->mu);
-  if (grpc_timer_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_trace)) {
     gpr_log(GPR_INFO, "TIMER %p: CANCEL pending=%s", timer,
             timer->pending ? "true" : "false");
   }
@@ -487,7 +487,7 @@
 /* Rebalances the timer shard by computing a new 'queue_deadline_cap' and moving
    all relevant timers in shard->list (i.e timers with deadlines earlier than
    'queue_deadline_cap') into into shard->heap.
-   Returns 'true' if shard->heap has atleast ONE element
+   Returns 'true' if shard->heap has at least ONE element
    REQUIRES: shard->mu locked */
 static bool refill_heap(timer_shard* shard, grpc_millis now) {
   /* Compute the new queue window width and bound by the limits: */
@@ -504,7 +504,7 @@
       saturating_add(GPR_MAX(now, shard->queue_deadline_cap),
                      static_cast<grpc_millis>(deadline_delta * 1000.0));
 
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     gpr_log(GPR_INFO, "  .. shard[%d]->queue_deadline_cap --> %" PRId64,
             static_cast<int>(shard - g_shards), shard->queue_deadline_cap);
   }
@@ -512,7 +512,7 @@
     next = timer->next;
 
     if (timer->deadline < shard->queue_deadline_cap) {
-      if (grpc_timer_check_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
         gpr_log(GPR_INFO, "  .. add timer with deadline %" PRId64 " to heap",
                 timer->deadline);
       }
@@ -529,7 +529,7 @@
 static grpc_timer* pop_one(timer_shard* shard, grpc_millis now) {
   grpc_timer* timer;
   for (;;) {
-    if (grpc_timer_check_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
       gpr_log(GPR_INFO, "  .. shard[%d]: heap_empty=%s",
               static_cast<int>(shard - g_shards),
               grpc_timer_heap_is_empty(&shard->heap) ? "true" : "false");
@@ -539,13 +539,13 @@
       if (!refill_heap(shard, now)) return nullptr;
     }
     timer = grpc_timer_heap_top(&shard->heap);
-    if (grpc_timer_check_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
       gpr_log(GPR_INFO,
               "  .. check top timer deadline=%" PRId64 " now=%" PRId64,
               timer->deadline, now);
     }
     if (timer->deadline > now) return nullptr;
-    if (grpc_timer_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_trace)) {
       gpr_log(GPR_INFO, "TIMER %p: FIRE %" PRId64 "ms late via %s scheduler",
               timer, now - timer->deadline,
               timer->closure->scheduler->vtable->name);
@@ -569,7 +569,7 @@
   }
   *new_min_deadline = compute_min_deadline(shard);
   gpr_mu_unlock(&shard->mu);
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     gpr_log(GPR_INFO, "  .. shard[%d] popped %" PRIdPTR,
             static_cast<int>(shard - g_shards), n);
   }
@@ -606,7 +606,7 @@
     gpr_mu_lock(&g_shared_mutables.mu);
     result = GRPC_TIMERS_CHECKED_AND_EMPTY;
 
-    if (grpc_timer_check_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
       gpr_log(GPR_INFO, "  .. shard[%d]->min_deadline = %" PRId64,
               static_cast<int>(g_shard_queue[0] - g_shards),
               g_shard_queue[0]->min_deadline);
@@ -624,7 +624,7 @@
         result = GRPC_TIMERS_FIRED;
       }
 
-      if (grpc_timer_check_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
         gpr_log(GPR_INFO,
                 "  .. result --> %d"
                 ", shard[%d]->min_deadline %" PRId64 " --> %" PRId64
@@ -691,7 +691,7 @@
     if (next != nullptr) {
       *next = GPR_MIN(*next, min_timer);
     }
-    if (grpc_timer_check_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
       gpr_log(GPR_INFO, "TIMER CHECK SKIP: now=%" PRId64 " min_timer=%" PRId64,
               now, min_timer);
     }
@@ -704,7 +704,7 @@
           : GRPC_ERROR_CREATE_FROM_STATIC_STRING("Shutting down timer system");
 
   // tracing
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     char* next_str;
     if (next == nullptr) {
       next_str = gpr_strdup("NULL");
@@ -728,7 +728,7 @@
   grpc_timer_check_result r =
       run_some_expired_timers(now, next, shutdown_error);
   // tracing
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     char* next_str;
     if (next == nullptr) {
       next_str = gpr_strdup("NULL");
diff --git a/src/core/lib/iomgr/timer_manager.cc b/src/core/lib/iomgr/timer_manager.cc
index 4469db7..bdf5490 100644
--- a/src/core/lib/iomgr/timer_manager.cc
+++ b/src/core/lib/iomgr/timer_manager.cc
@@ -90,7 +90,7 @@
   ++g_waiter_count;
   ++g_thread_count;
   gpr_mu_unlock(&g_mu);
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     gpr_log(GPR_INFO, "Spawn timer thread");
   }
   completed_thread* ct =
@@ -126,7 +126,7 @@
     // if there's no thread waiting with a timeout, kick an existing untimed
     // waiter so that the next deadline is not missed
     if (!g_has_timed_waiter) {
-      if (grpc_timer_check_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
         gpr_log(GPR_INFO, "kick untimed waiter");
       }
       gpr_cv_signal(&g_cv_wait);
@@ -134,7 +134,7 @@
     gpr_mu_unlock(&g_mu);
   }
   // without our lock, flush the exec_ctx
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     gpr_log(GPR_INFO, "flush exec_ctx");
   }
   grpc_core::ExecCtx::Get()->Flush();
@@ -189,7 +189,7 @@
         g_has_timed_waiter = true;
         g_timed_waiter_deadline = next;
 
-        if (grpc_timer_check_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
           grpc_millis wait_time = next - grpc_core::ExecCtx::Get()->Now();
           gpr_log(GPR_INFO, "sleep for a %" PRId64 " milliseconds", wait_time);
         }
@@ -198,7 +198,8 @@
       }
     }
 
-    if (grpc_timer_check_trace.enabled() && next == GRPC_MILLIS_INF_FUTURE) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace) &&
+        next == GRPC_MILLIS_INF_FUTURE) {
       gpr_log(GPR_INFO, "sleep until kicked");
     }
 
@@ -210,7 +211,7 @@
     gpr_cv_wait(&g_cv_wait, &g_mu,
                 grpc_millis_to_timespec(next, GPR_CLOCK_MONOTONIC));
 
-    if (grpc_timer_check_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
       gpr_log(GPR_INFO, "wait ended: was_timed:%d kicked:%d",
               my_timed_waiter_generation == g_timed_waiter_generation,
               g_kicked);
@@ -255,7 +256,7 @@
 
            Consequently, we can just sleep forever here and be happy at some
            saved wakeup cycles. */
-        if (grpc_timer_check_trace.enabled()) {
+        if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
           gpr_log(GPR_INFO, "timers not checked: expect another thread to");
         }
         next = GRPC_MILLIS_INF_FUTURE;
@@ -281,7 +282,7 @@
   ct->next = g_completed_threads;
   g_completed_threads = ct;
   gpr_mu_unlock(&g_mu);
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     gpr_log(GPR_INFO, "End timer thread");
   }
 }
@@ -327,18 +328,18 @@
 
 static void stop_threads(void) {
   gpr_mu_lock(&g_mu);
-  if (grpc_timer_check_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
     gpr_log(GPR_INFO, "stop timer threads: threaded=%d", g_threaded);
   }
   if (g_threaded) {
     g_threaded = false;
     gpr_cv_broadcast(&g_cv_wait);
-    if (grpc_timer_check_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
       gpr_log(GPR_INFO, "num timer threads: %d", g_thread_count);
     }
     while (g_thread_count > 0) {
       gpr_cv_wait(&g_cv_shutdown, &g_mu, gpr_inf_future(GPR_CLOCK_MONOTONIC));
-      if (grpc_timer_check_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_timer_check_trace)) {
         gpr_log(GPR_INFO, "num timer threads: %d", g_thread_count);
       }
       gc_completed_threads();
diff --git a/src/core/lib/profiling/basic_timers.cc b/src/core/lib/profiling/basic_timers.cc
index b19ad9f..37689fe 100644
--- a/src/core/lib/profiling/basic_timers.cc
+++ b/src/core/lib/profiling/basic_timers.cc
@@ -31,7 +31,8 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gprpp/global_config.h"
+#include "src/core/lib/profiling/timers.h"
 
 typedef enum { BEGIN = '{', END = '}', MARK = '.' } marker_type;
 
@@ -74,11 +75,16 @@
 static int g_next_thread_id;
 static int g_writing_enabled = 1;
 
+GPR_GLOBAL_CONFIG_DEFINE_STRING(grpc_latency_trace, "latency_trace.txt",
+                                "Output file name for latency trace")
+
 static const char* output_filename() {
   if (output_filename_or_null == NULL) {
-    output_filename_or_null = gpr_getenv("LATENCY_TRACE");
-    if (output_filename_or_null == NULL ||
-        strlen(output_filename_or_null) == 0) {
+    grpc_core::UniquePtr<char> value =
+        GPR_GLOBAL_CONFIG_GET(grpc_latency_trace);
+    if (strlen(value.get()) > 0) {
+      output_filename_or_null = value.release();
+    } else {
       output_filename_or_null = "latency_trace.txt";
     }
   }
diff --git a/src/core/lib/security/context/security_context.cc b/src/core/lib/security/context/security_context.cc
index 8443ee0..ef60165 100644
--- a/src/core/lib/security/context/security_context.cc
+++ b/src/core/lib/security/context/security_context.cc
@@ -21,8 +21,8 @@
 #include <string.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/arena.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gprpp/arena.h"
 #include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/security/context/security_context.h"
@@ -102,9 +102,9 @@
 }
 
 grpc_client_security_context* grpc_client_security_context_create(
-    gpr_arena* arena, grpc_call_credentials* creds) {
-  return new (gpr_arena_alloc(arena, sizeof(grpc_client_security_context)))
-      grpc_client_security_context(creds != nullptr ? creds->Ref() : nullptr);
+    grpc_core::Arena* arena, grpc_call_credentials* creds) {
+  return arena->New<grpc_client_security_context>(
+      creds != nullptr ? creds->Ref() : nullptr);
 }
 
 void grpc_client_security_context_destroy(void* ctx) {
@@ -123,9 +123,8 @@
 }
 
 grpc_server_security_context* grpc_server_security_context_create(
-    gpr_arena* arena) {
-  return new (gpr_arena_alloc(arena, sizeof(grpc_server_security_context)))
-      grpc_server_security_context();
+    grpc_core::Arena* arena) {
+  return arena->New<grpc_server_security_context>();
 }
 
 void grpc_server_security_context_destroy(void* ctx) {
diff --git a/src/core/lib/security/context/security_context.h b/src/core/lib/security/context/security_context.h
index b43ee5e..b199108 100644
--- a/src/core/lib/security/context/security_context.h
+++ b/src/core/lib/security/context/security_context.h
@@ -21,6 +21,7 @@
 
 #include <grpc/support/port_platform.h>
 
+#include "src/core/lib/gprpp/arena.h"
 #include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/pollset.h"
@@ -28,8 +29,6 @@
 
 extern grpc_core::DebugOnlyTraceFlag grpc_trace_auth_context_refcount;
 
-struct gpr_arena;
-
 /* --- grpc_auth_context ---
 
    High level authentication context object. Can optionally be chained. */
@@ -121,7 +120,7 @@
 };
 
 grpc_client_security_context* grpc_client_security_context_create(
-    gpr_arena* arena, grpc_call_credentials* creds);
+    grpc_core::Arena* arena, grpc_call_credentials* creds);
 void grpc_client_security_context_destroy(void* ctx);
 
 /* --- grpc_server_security_context ---
@@ -137,7 +136,7 @@
 };
 
 grpc_server_security_context* grpc_server_security_context_create(
-    gpr_arena* arena);
+    grpc_core::Arena* arena);
 void grpc_server_security_context_destroy(void* ctx);
 
 /* --- Channel args for auth context --- */
diff --git a/src/core/lib/security/credentials/jwt/jwt_credentials.cc b/src/core/lib/security/credentials/jwt/jwt_credentials.cc
index 70fe45e..df1d05c 100644
--- a/src/core/lib/security/credentials/jwt/jwt_credentials.cc
+++ b/src/core/lib/security/credentials/jwt/jwt_credentials.cc
@@ -160,7 +160,7 @@
 
 grpc_call_credentials* grpc_service_account_jwt_access_credentials_create(
     const char* json_key, gpr_timespec token_lifetime, void* reserved) {
-  if (grpc_api_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace)) {
     char* clean_json = redact_private_key(json_key);
     gpr_log(GPR_INFO,
             "grpc_service_account_jwt_access_credentials_create("
diff --git a/src/core/lib/security/credentials/jwt/jwt_verifier.cc b/src/core/lib/security/credentials/jwt/jwt_verifier.cc
index d573c30..5b120ed 100644
--- a/src/core/lib/security/credentials/jwt/jwt_verifier.cc
+++ b/src/core/lib/security/credentials/jwt/jwt_verifier.cc
@@ -624,9 +624,8 @@
     gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed.");
     goto end;
   }
-  if (EVP_DigestVerifyFinal(
-          md_ctx, const_cast<uint8_t*>(GRPC_SLICE_START_PTR(signature)),
-          GRPC_SLICE_LENGTH(signature)) != 1) {
+  if (EVP_DigestVerifyFinal(md_ctx, GRPC_SLICE_START_PTR(signature),
+                            GRPC_SLICE_LENGTH(signature)) != 1) {
     gpr_log(GPR_ERROR, "JWT signature verification failed.");
     goto end;
   }
diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
index b9af757..b001868 100644
--- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
+++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
@@ -459,7 +459,7 @@
     const char* json_refresh_token, void* reserved) {
   grpc_auth_refresh_token token =
       grpc_auth_refresh_token_create_from_string(json_refresh_token);
-  if (grpc_api_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace)) {
     char* loggable_token = create_loggable_refresh_token(&token);
     gpr_log(GPR_INFO,
             "grpc_refresh_token_credentials_create(json_refresh_token=%s, "
diff --git a/src/core/lib/security/credentials/plugin/plugin_credentials.cc b/src/core/lib/security/credentials/plugin/plugin_credentials.cc
index 59fecbc..333366d 100644
--- a/src/core/lib/security/credentials/plugin/plugin_credentials.cc
+++ b/src/core/lib/security/credentials/plugin/plugin_credentials.cc
@@ -119,7 +119,7 @@
                               GRPC_EXEC_CTX_FLAG_THREAD_RESOURCE_LOOP);
   grpc_plugin_credentials::pending_request* r =
       static_cast<grpc_plugin_credentials::pending_request*>(request);
-  if (grpc_plugin_credentials_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_plugin_credentials_trace)) {
     gpr_log(GPR_INFO,
             "plugin_credentials[%p]: request %p: plugin returned "
             "asynchronously",
@@ -132,7 +132,7 @@
     grpc_error* error =
         process_plugin_result(r, md, num_md, status, error_details);
     GRPC_CLOSURE_SCHED(r->on_request_metadata, error);
-  } else if (grpc_plugin_credentials_trace.enabled()) {
+  } else if (GRPC_TRACE_FLAG_ENABLED(grpc_plugin_credentials_trace)) {
     gpr_log(GPR_INFO,
             "plugin_credentials[%p]: request %p: plugin was previously "
             "cancelled",
@@ -162,7 +162,7 @@
     pending_requests_ = request;
     gpr_mu_unlock(&mu_);
     // Invoke the plugin.  The callback holds a ref to us.
-    if (grpc_plugin_credentials_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_plugin_credentials_trace)) {
       gpr_log(GPR_INFO, "plugin_credentials[%p]: request %p: invoking plugin",
               this, request);
     }
@@ -174,7 +174,7 @@
     if (!plugin_.get_metadata(
             plugin_.state, context, plugin_md_request_metadata_ready, request,
             creds_md, &num_creds_md, &status, &error_details)) {
-      if (grpc_plugin_credentials_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_plugin_credentials_trace)) {
         gpr_log(GPR_INFO,
                 "plugin_credentials[%p]: request %p: plugin will return "
                 "asynchronously",
@@ -189,7 +189,7 @@
     // asynchronously by plugin_cancel_get_request_metadata(), so return
     // false.  Otherwise, process the result.
     if (request->cancelled) {
-      if (grpc_plugin_credentials_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_plugin_credentials_trace)) {
         gpr_log(GPR_INFO,
                 "plugin_credentials[%p]: request %p was cancelled, error "
                 "will be returned asynchronously",
@@ -197,7 +197,7 @@
       }
       retval = false;
     } else {
-      if (grpc_plugin_credentials_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_plugin_credentials_trace)) {
         gpr_log(GPR_INFO,
                 "plugin_credentials[%p]: request %p: plugin returned "
                 "synchronously",
@@ -223,7 +223,7 @@
   for (pending_request* pending_request = pending_requests_;
        pending_request != nullptr; pending_request = pending_request->next) {
     if (pending_request->md_array == md_array) {
-      if (grpc_plugin_credentials_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_plugin_credentials_trace)) {
         gpr_log(GPR_INFO, "plugin_credentials[%p]: cancelling request %p", this,
                 pending_request);
       }
diff --git a/src/core/lib/security/security_connector/load_system_roots_linux.cc b/src/core/lib/security/security_connector/load_system_roots_linux.cc
index 924fa8a..82d5bf6 100644
--- a/src/core/lib/security/security_connector/load_system_roots_linux.cc
+++ b/src/core/lib/security/security_connector/load_system_roots_linux.cc
@@ -38,12 +38,15 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/iomgr/load_file.h"
 
+GPR_GLOBAL_CONFIG_DEFINE_STRING(grpc_system_ssl_roots_dir, "",
+                                "Custom directory to SSL Roots");
+
 namespace grpc_core {
 namespace {
 
@@ -139,10 +142,9 @@
 grpc_slice LoadSystemRootCerts() {
   grpc_slice result = grpc_empty_slice();
   // Prioritize user-specified custom directory if flag is set.
-  char* custom_dir = gpr_getenv("GRPC_SYSTEM_SSL_ROOTS_DIR");
-  if (custom_dir != nullptr) {
-    result = CreateRootCertsBundle(custom_dir);
-    gpr_free(custom_dir);
+  UniquePtr<char> custom_dir = GPR_GLOBAL_CONFIG_GET(grpc_system_ssl_roots_dir);
+  if (strlen(custom_dir.get()) > 0) {
+    result = CreateRootCertsBundle(custom_dir.get());
   }
   // If the custom directory is empty/invalid/not specified, fallback to
   // distribution-specific directory.
diff --git a/src/core/lib/security/security_connector/security_connector.cc b/src/core/lib/security/security_connector/security_connector.cc
index 96a1960..47c0ad5 100644
--- a/src/core/lib/security/security_connector/security_connector.cc
+++ b/src/core/lib/security/security_connector/security_connector.cc
@@ -28,7 +28,6 @@
 #include "src/core/ext/transport/chttp2/alpn/alpn.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/handshaker.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/iomgr/load_file.h"
diff --git a/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc b/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc
index ad492f36..e76f4f1 100644
--- a/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc
+++ b/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc
@@ -314,7 +314,6 @@
   bool try_fetch_ssl_server_credentials() {
     grpc_ssl_server_certificate_config* certificate_config = nullptr;
     bool status;
-
     if (!has_cert_config_fetcher()) return false;
 
     grpc_ssl_server_credentials* server_creds =
@@ -374,7 +373,9 @@
     options.num_alpn_protocols = static_cast<uint16_t>(num_alpn_protocols);
     tsi_result result = tsi_create_ssl_server_handshaker_factory_with_options(
         &options, &new_handshaker_factory);
-    gpr_free((void*)options.pem_key_cert_pairs);
+    grpc_tsi_ssl_pem_key_cert_pairs_destroy(
+        const_cast<tsi_ssl_pem_key_cert_pair*>(options.pem_key_cert_pairs),
+        options.num_key_cert_pairs);
     gpr_free((void*)alpn_protocol_strings);
 
     if (result != TSI_OK) {
diff --git a/src/core/lib/security/security_connector/ssl_utils.cc b/src/core/lib/security/security_connector/ssl_utils.cc
index c9af5ca..cb0d543 100644
--- a/src/core/lib/security/security_connector/ssl_utils.cc
+++ b/src/core/lib/security/security_connector/ssl_utils.cc
@@ -27,9 +27,9 @@
 
 #include "src/core/ext/transport/chttp2/alpn/alpn.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/load_file.h"
 #include "src/core/lib/security/context/security_context.h"
@@ -45,11 +45,16 @@
     INSTALL_PREFIX "/share/grpc/roots.pem";
 #endif
 
-/** Environment variable used as a flag to enable/disable loading system root
+/** Config variable that points to the default SSL roots file. This file
+   must be a PEM encoded file with all the roots such as the one that can be
+   downloaded from https://pki.google.com/roots.pem.  */
+GPR_GLOBAL_CONFIG_DEFINE_STRING(grpc_default_ssl_roots_file_path, "",
+                                "Path to the default SSL roots file.");
+
+/** Config variable used as a flag to enable/disable loading system root
     certificates from the OS trust store. */
-#ifndef GRPC_NOT_USE_SYSTEM_SSL_ROOTS_ENV_VAR
-#define GRPC_NOT_USE_SYSTEM_SSL_ROOTS_ENV_VAR "GRPC_NOT_USE_SYSTEM_SSL_ROOTS"
-#endif
+GPR_GLOBAL_CONFIG_DEFINE_BOOL(grpc_not_use_system_ssl_roots, false,
+                              "Disable loading system root certificates.");
 
 #ifndef TSI_OPENSSL_ALPN_SUPPORT
 #define TSI_OPENSSL_ALPN_SUPPORT 1
@@ -65,20 +70,22 @@
 
 /* -- Cipher suites. -- */
 
-/* Defines the cipher suites that we accept by default. All these cipher suites
-   are compliant with HTTP2. */
-#define GRPC_SSL_CIPHER_SUITES     \
-  "ECDHE-ECDSA-AES128-GCM-SHA256:" \
-  "ECDHE-ECDSA-AES256-GCM-SHA384:" \
-  "ECDHE-RSA-AES128-GCM-SHA256:"   \
-  "ECDHE-RSA-AES256-GCM-SHA384"
-
 static gpr_once cipher_suites_once = GPR_ONCE_INIT;
 static const char* cipher_suites = nullptr;
 
+// All cipher suites for default are compliant with HTTP2.
+GPR_GLOBAL_CONFIG_DEFINE_STRING(
+    grpc_ssl_cipher_suites,
+    "ECDHE-ECDSA-AES128-GCM-SHA256:"
+    "ECDHE-ECDSA-AES256-GCM-SHA384:"
+    "ECDHE-RSA-AES128-GCM-SHA256:"
+    "ECDHE-RSA-AES256-GCM-SHA384",
+    "A colon separated list of cipher suites to use with OpenSSL")
+
 static void init_cipher_suites(void) {
-  char* overridden = gpr_getenv("GRPC_SSL_CIPHER_SUITES");
-  cipher_suites = overridden != nullptr ? overridden : GRPC_SSL_CIPHER_SUITES;
+  grpc_core::UniquePtr<char> value =
+      GPR_GLOBAL_CONFIG_GET(grpc_ssl_cipher_suites);
+  cipher_suites = value.release();
 }
 
 /* --- Util --- */
@@ -428,17 +435,14 @@
 
 grpc_slice DefaultSslRootStore::ComputePemRootCerts() {
   grpc_slice result = grpc_empty_slice();
-  char* not_use_system_roots_env_value =
-      gpr_getenv(GRPC_NOT_USE_SYSTEM_SSL_ROOTS_ENV_VAR);
-  const bool not_use_system_roots = gpr_is_true(not_use_system_roots_env_value);
-  gpr_free(not_use_system_roots_env_value);
-  // First try to load the roots from the environment.
-  char* default_root_certs_path =
-      gpr_getenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR);
-  if (default_root_certs_path != nullptr) {
-    GRPC_LOG_IF_ERROR("load_file",
-                      grpc_load_file(default_root_certs_path, 1, &result));
-    gpr_free(default_root_certs_path);
+  const bool not_use_system_roots =
+      GPR_GLOBAL_CONFIG_GET(grpc_not_use_system_ssl_roots);
+  // First try to load the roots from the configuration.
+  UniquePtr<char> default_root_certs_path =
+      GPR_GLOBAL_CONFIG_GET(grpc_default_ssl_roots_file_path);
+  if (strlen(default_root_certs_path.get()) > 0) {
+    GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(default_root_certs_path.get(), 1, &result));
   }
   // Try overridden roots if needed.
   grpc_ssl_roots_override_result ovrd_res = GRPC_SSL_ROOTS_OVERRIDE_FAIL;
diff --git a/src/core/lib/security/security_connector/ssl_utils.h b/src/core/lib/security/security_connector/ssl_utils.h
index 080e277..1765a34 100644
--- a/src/core/lib/security/security_connector/ssl_utils.h
+++ b/src/core/lib/security/security_connector/ssl_utils.h
@@ -26,6 +26,7 @@
 #include <grpc/grpc_security.h>
 #include <grpc/slice_buffer.h>
 
+#include "src/core/lib/gprpp/global_config.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/error.h"
 #include "src/core/lib/security/security_connector/security_connector.h"
@@ -33,7 +34,10 @@
 #include "src/core/tsi/transport_security.h"
 #include "src/core/tsi/transport_security_interface.h"
 
-/* --- Util. --- */
+GPR_GLOBAL_CONFIG_DECLARE_STRING(grpc_default_ssl_roots_file_path);
+GPR_GLOBAL_CONFIG_DECLARE_BOOL(grpc_not_use_system_ssl_roots);
+
+/* --- Util --- */
 
 /* --- URL schemes. --- */
 #define GRPC_SSL_URL_SCHEME "https"
diff --git a/src/core/lib/security/transport/client_auth_filter.cc b/src/core/lib/security/transport/client_auth_filter.cc
index f90c92e..0c40dd7 100644
--- a/src/core/lib/security/transport/client_auth_filter.cc
+++ b/src/core/lib/security/transport/client_auth_filter.cc
@@ -92,7 +92,7 @@
   }
 
   grpc_call_stack* owning_call;
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   grpc_core::RefCountedPtr<grpc_call_credentials> creds;
   grpc_slice host = grpc_empty_slice();
   grpc_slice method = grpc_empty_slice();
@@ -270,11 +270,9 @@
     GRPC_ERROR_UNREF(error);
   } else {
     // Async return; register cancellation closure with call combiner.
-    grpc_call_combiner_set_notify_on_cancel(
-        calld->call_combiner,
-        GRPC_CLOSURE_INIT(&calld->get_request_metadata_cancel_closure,
-                          cancel_get_request_metadata, elem,
-                          grpc_schedule_on_exec_ctx));
+    calld->call_combiner->SetNotifyOnCancel(GRPC_CLOSURE_INIT(
+        &calld->get_request_metadata_cancel_closure,
+        cancel_get_request_metadata, elem, grpc_schedule_on_exec_ctx));
   }
 }
 
@@ -345,11 +343,9 @@
         GRPC_ERROR_UNREF(error);
       } else {
         // Async return; register cancellation closure with call combiner.
-        grpc_call_combiner_set_notify_on_cancel(
-            calld->call_combiner,
-            GRPC_CLOSURE_INIT(&calld->check_call_host_cancel_closure,
-                              cancel_check_call_host, elem,
-                              grpc_schedule_on_exec_ctx));
+        calld->call_combiner->SetNotifyOnCancel(GRPC_CLOSURE_INIT(
+            &calld->check_call_host_cancel_closure, cancel_check_call_host,
+            elem, grpc_schedule_on_exec_ctx));
       }
       gpr_free(call_host);
       return; /* early exit */
diff --git a/src/core/lib/security/transport/secure_endpoint.cc b/src/core/lib/security/transport/secure_endpoint.cc
index 2a86249..0aac7d8 100644
--- a/src/core/lib/security/transport/secure_endpoint.cc
+++ b/src/core/lib/security/transport/secure_endpoint.cc
@@ -113,7 +113,7 @@
   secure_endpoint_ref((ep), (reason), __FILE__, __LINE__)
 static void secure_endpoint_unref(secure_endpoint* ep, const char* reason,
                                   const char* file, int line) {
-  if (grpc_trace_secure_endpoint.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_secure_endpoint)) {
     gpr_atm val = gpr_atm_no_barrier_load(&ep->ref.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
             "SECENDP unref %p : %s %" PRIdPTR " -> %" PRIdPTR, ep, reason, val,
@@ -126,7 +126,7 @@
 
 static void secure_endpoint_ref(secure_endpoint* ep, const char* reason,
                                 const char* file, int line) {
-  if (grpc_trace_secure_endpoint.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_secure_endpoint)) {
     gpr_atm val = gpr_atm_no_barrier_load(&ep->ref.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
             "SECENDP   ref %p : %s %" PRIdPTR " -> %" PRIdPTR, ep, reason, val,
@@ -155,7 +155,7 @@
 }
 
 static void call_read_cb(secure_endpoint* ep, grpc_error* error) {
-  if (grpc_trace_secure_endpoint.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_secure_endpoint)) {
     size_t i;
     for (i = 0; i < ep->read_buffer->count; i++) {
       char* data = grpc_dump_slice(ep->read_buffer->slices[i],
@@ -292,7 +292,7 @@
 
   grpc_slice_buffer_reset_and_unref_internal(&ep->output_buffer);
 
-  if (grpc_trace_secure_endpoint.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_secure_endpoint)) {
     for (i = 0; i < slices->count; i++) {
       char* data =
           grpc_dump_slice(slices->slices[i], GPR_DUMP_HEX | GPR_DUMP_ASCII);
diff --git a/src/core/lib/security/transport/server_auth_filter.cc b/src/core/lib/security/transport/server_auth_filter.cc
index 81b9c2c..43509e6 100644
--- a/src/core/lib/security/transport/server_auth_filter.cc
+++ b/src/core/lib/security/transport/server_auth_filter.cc
@@ -74,7 +74,7 @@
 
   ~call_data() { GRPC_ERROR_UNREF(recv_initial_metadata_error); }
 
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   grpc_call_stack* owning_call;
   grpc_transport_stream_op_batch* recv_initial_metadata_batch;
   grpc_closure* original_recv_initial_metadata_ready;
@@ -219,8 +219,7 @@
       // to drop the call combiner early if we get cancelled.
       GRPC_CLOSURE_INIT(&calld->cancel_closure, cancel_call, elem,
                         grpc_schedule_on_exec_ctx);
-      grpc_call_combiner_set_notify_on_cancel(calld->call_combiner,
-                                              &calld->cancel_closure);
+      calld->call_combiner->SetNotifyOnCancel(&calld->cancel_closure);
       GRPC_CALL_STACK_REF(calld->owning_call, "server_auth_metadata");
       calld->md = metadata_batch_to_md_array(
           batch->payload->recv_initial_metadata.recv_initial_metadata);
diff --git a/src/core/lib/slice/slice.cc b/src/core/lib/slice/slice.cc
index ac935f1..2bf2b0f 100644
--- a/src/core/lib/slice/slice.cc
+++ b/src/core/lib/slice/slice.cc
@@ -26,6 +26,7 @@
 
 #include <string.h>
 
+#include "src/core/lib/gprpp/memory.h"
 #include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
 
@@ -67,17 +68,10 @@
 
 /* grpc_slice_from_static_string support structure - a refcount that does
    nothing */
-static void noop_ref(void* unused) {}
-static void noop_unref(void* unused) {}
-
-static const grpc_slice_refcount_vtable noop_refcount_vtable = {
-    noop_ref, noop_unref, grpc_slice_default_eq_impl,
-    grpc_slice_default_hash_impl};
-static grpc_slice_refcount noop_refcount = {&noop_refcount_vtable,
-                                            &noop_refcount};
+static grpc_slice_refcount NoopRefcount;
 
 size_t grpc_slice_memory_usage(grpc_slice s) {
-  if (s.refcount == nullptr || s.refcount == &noop_refcount) {
+  if (s.refcount == nullptr || s.refcount == &NoopRefcount) {
     return 0;
   } else {
     return s.data.refcounted.length;
@@ -86,7 +80,7 @@
 
 grpc_slice grpc_slice_from_static_buffer(const void* s, size_t len) {
   grpc_slice slice;
-  slice.refcount = &noop_refcount;
+  slice.refcount = &NoopRefcount;
   slice.data.refcounted.bytes = (uint8_t*)s;
   slice.data.refcounted.length = len;
   return slice;
@@ -96,45 +90,43 @@
   return grpc_slice_from_static_buffer(s, strlen(s));
 }
 
+namespace grpc_core {
+
 /* grpc_slice_new support structures - we create a refcount object extended
    with the user provided data pointer & destroy function */
-typedef struct new_slice_refcount {
-  grpc_slice_refcount rc;
-  gpr_refcount refs;
-  void (*user_destroy)(void*);
-  void* user_data;
-} new_slice_refcount;
-
-static void new_slice_ref(void* p) {
-  new_slice_refcount* r = static_cast<new_slice_refcount*>(p);
-  gpr_ref(&r->refs);
-}
-
-static void new_slice_unref(void* p) {
-  new_slice_refcount* r = static_cast<new_slice_refcount*>(p);
-  if (gpr_unref(&r->refs)) {
-    r->user_destroy(r->user_data);
-    gpr_free(r);
+class NewSliceRefcount {
+ public:
+  static void Destroy(void* arg) {
+    Delete(static_cast<NewSliceRefcount*>(arg));
   }
-}
 
-static const grpc_slice_refcount_vtable new_slice_vtable = {
-    new_slice_ref, new_slice_unref, grpc_slice_default_eq_impl,
-    grpc_slice_default_hash_impl};
+  NewSliceRefcount(void (*destroy)(void*), void* user_data)
+      : rc_(grpc_slice_refcount::Type::REGULAR, &refs_, Destroy, this, &rc_),
+        user_destroy_(destroy),
+        user_data_(user_data) {}
+
+  GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
+
+  grpc_slice_refcount* base_refcount() { return &rc_; }
+
+ private:
+  ~NewSliceRefcount() { user_destroy_(user_data_); }
+
+  grpc_slice_refcount rc_;
+  RefCount refs_;
+  void (*user_destroy_)(void*);
+  void* user_data_;
+};
+
+}  // namespace grpc_core
 
 grpc_slice grpc_slice_new_with_user_data(void* p, size_t len,
                                          void (*destroy)(void*),
                                          void* user_data) {
   grpc_slice slice;
-  new_slice_refcount* rc =
-      static_cast<new_slice_refcount*>(gpr_malloc(sizeof(new_slice_refcount)));
-  gpr_ref_init(&rc->refs, 1);
-  rc->rc.vtable = &new_slice_vtable;
-  rc->rc.sub_refcount = &rc->rc;
-  rc->user_destroy = destroy;
-  rc->user_data = user_data;
-
-  slice.refcount = &rc->rc;
+  slice.refcount =
+      grpc_core::New<grpc_core::NewSliceRefcount>(destroy, user_data)
+          ->base_refcount();
   slice.data.refcounted.bytes = static_cast<uint8_t*>(p);
   slice.data.refcounted.length = len;
   return slice;
@@ -145,46 +137,45 @@
   return grpc_slice_new_with_user_data(p, len, destroy, p);
 }
 
+namespace grpc_core {
 /* grpc_slice_new_with_len support structures - we create a refcount object
    extended with the user provided data pointer & destroy function */
-typedef struct new_with_len_slice_refcount {
-  grpc_slice_refcount rc;
-  gpr_refcount refs;
-  void* user_data;
-  size_t user_length;
-  void (*user_destroy)(void*, size_t);
-} new_with_len_slice_refcount;
 
-static void new_with_len_ref(void* p) {
-  new_with_len_slice_refcount* r = static_cast<new_with_len_slice_refcount*>(p);
-  gpr_ref(&r->refs);
-}
-
-static void new_with_len_unref(void* p) {
-  new_with_len_slice_refcount* r = static_cast<new_with_len_slice_refcount*>(p);
-  if (gpr_unref(&r->refs)) {
-    r->user_destroy(r->user_data, r->user_length);
-    gpr_free(r);
+class NewWithLenSliceRefcount {
+ public:
+  static void Destroy(void* arg) {
+    Delete(static_cast<NewWithLenSliceRefcount*>(arg));
   }
-}
 
-static const grpc_slice_refcount_vtable new_with_len_vtable = {
-    new_with_len_ref, new_with_len_unref, grpc_slice_default_eq_impl,
-    grpc_slice_default_hash_impl};
+  NewWithLenSliceRefcount(void (*destroy)(void*, size_t), void* user_data,
+                          size_t user_length)
+      : rc_(grpc_slice_refcount::Type::REGULAR, &refs_, Destroy, this, &rc_),
+        user_data_(user_data),
+        user_length_(user_length),
+        user_destroy_(destroy) {}
+
+  GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
+
+  grpc_slice_refcount* base_refcount() { return &rc_; }
+
+ private:
+  ~NewWithLenSliceRefcount() { user_destroy_(user_data_, user_length_); }
+
+  grpc_slice_refcount rc_;
+  RefCount refs_;
+  void* user_data_;
+  size_t user_length_;
+  void (*user_destroy_)(void*, size_t);
+};
+
+}  // namespace grpc_core
 
 grpc_slice grpc_slice_new_with_len(void* p, size_t len,
                                    void (*destroy)(void*, size_t)) {
   grpc_slice slice;
-  new_with_len_slice_refcount* rc = static_cast<new_with_len_slice_refcount*>(
-      gpr_malloc(sizeof(new_with_len_slice_refcount)));
-  gpr_ref_init(&rc->refs, 1);
-  rc->rc.vtable = &new_with_len_vtable;
-  rc->rc.sub_refcount = &rc->rc;
-  rc->user_destroy = destroy;
-  rc->user_data = p;
-  rc->user_length = len;
-
-  slice.refcount = &rc->rc;
+  slice.refcount =
+      grpc_core::New<grpc_core::NewWithLenSliceRefcount>(destroy, p, len)
+          ->base_refcount();
   slice.data.refcounted.bytes = static_cast<uint8_t*>(p);
   slice.data.refcounted.length = len;
   return slice;
@@ -203,39 +194,28 @@
 
 namespace {
 
-struct MallocRefCount {
-  MallocRefCount(const grpc_slice_refcount_vtable* vtable) {
-    base.vtable = vtable;
-    base.sub_refcount = &base;
+class MallocRefCount {
+ public:
+  static void Destroy(void* arg) {
+    MallocRefCount* r = static_cast<MallocRefCount*>(arg);
+    r->~MallocRefCount();
+    gpr_free(r);
   }
 
-  void Ref() { refs.Ref(); }
-  void Unref() {
-    if (refs.Unref()) {
-      gpr_free(this);
-    }
-  }
+  MallocRefCount()
+      : base_(grpc_slice_refcount::Type::REGULAR, &refs_, Destroy, this,
+              &base_) {}
+  ~MallocRefCount() = default;
 
-  grpc_slice_refcount base;
-  grpc_core::RefCount refs;
+  grpc_slice_refcount* base_refcount() { return &base_; }
+
+ private:
+  grpc_slice_refcount base_;
+  grpc_core::RefCount refs_;
 };
 
 }  // namespace
 
-static void malloc_ref(void* p) {
-  MallocRefCount* r = static_cast<MallocRefCount*>(p);
-  r->Ref();
-}
-
-static void malloc_unref(void* p) {
-  MallocRefCount* r = static_cast<MallocRefCount*>(p);
-  r->Unref();
-}
-
-static const grpc_slice_refcount_vtable malloc_vtable = {
-    malloc_ref, malloc_unref, grpc_slice_default_eq_impl,
-    grpc_slice_default_hash_impl};
-
 grpc_slice grpc_slice_malloc_large(size_t length) {
   grpc_slice slice;
 
@@ -248,14 +228,16 @@
      refcount is a malloc_refcount
      bytes is an array of bytes of the requested length
      Both parts are placed in the same allocation returned from gpr_malloc */
-  void* data =
+  auto* rc =
       static_cast<MallocRefCount*>(gpr_malloc(sizeof(MallocRefCount) + length));
 
-  auto* rc = new (data) MallocRefCount(&malloc_vtable);
+  /* Initial refcount on rc is 1 - and it's up to the caller to release
+     this reference. */
+  new (rc) MallocRefCount();
 
   /* Build up the slice to be returned. */
   /* The slices refcount points back to the allocated block. */
-  slice.refcount = &rc->base;
+  slice.refcount = rc->base_refcount();
   /* The data bytes are placed immediately after the refcount struct */
   slice.data.refcounted.bytes = reinterpret_cast<uint8_t*>(rc + 1);
   /* And the length of the block is set to the requested length */
@@ -286,7 +268,7 @@
     GPR_ASSERT(source.data.refcounted.length >= end);
 
     /* Build the result */
-    subset.refcount = source.refcount->sub_refcount;
+    subset.refcount = source.refcount->sub_refcount();
     /* Point into the source array */
     subset.data.refcounted.bytes = source.data.refcounted.bytes + begin;
     subset.data.refcounted.length = end - begin;
@@ -312,7 +294,7 @@
   } else {
     subset = grpc_slice_sub_no_ref(source, begin, end);
     /* Bump the refcount */
-    subset.refcount->vtable->ref(subset.refcount);
+    subset.refcount->Ref();
   }
   return subset;
 }
@@ -340,23 +322,23 @@
       tail.data.inlined.length = static_cast<uint8_t>(tail_length);
       memcpy(tail.data.inlined.bytes, source->data.refcounted.bytes + split,
              tail_length);
-      source->refcount = source->refcount->sub_refcount;
+      source->refcount = source->refcount->sub_refcount();
     } else {
       /* Build the result */
       switch (ref_whom) {
         case GRPC_SLICE_REF_TAIL:
-          tail.refcount = source->refcount->sub_refcount;
-          source->refcount = &noop_refcount;
+          tail.refcount = source->refcount->sub_refcount();
+          source->refcount = &NoopRefcount;
           break;
         case GRPC_SLICE_REF_HEAD:
-          tail.refcount = &noop_refcount;
-          source->refcount = source->refcount->sub_refcount;
+          tail.refcount = &NoopRefcount;
+          source->refcount = source->refcount->sub_refcount();
           break;
         case GRPC_SLICE_REF_BOTH:
-          tail.refcount = source->refcount->sub_refcount;
-          source->refcount = source->refcount->sub_refcount;
+          tail.refcount = source->refcount->sub_refcount();
+          source->refcount = source->refcount->sub_refcount();
           /* Bump the refcount */
-          tail.refcount->vtable->ref(tail.refcount);
+          tail.refcount->Ref();
           break;
       }
       /* Point into the source array */
@@ -392,20 +374,20 @@
     head.refcount = nullptr;
     head.data.inlined.length = static_cast<uint8_t>(split);
     memcpy(head.data.inlined.bytes, source->data.refcounted.bytes, split);
-    source->refcount = source->refcount->sub_refcount;
+    source->refcount = source->refcount->sub_refcount();
     source->data.refcounted.bytes += split;
     source->data.refcounted.length -= split;
   } else {
     GPR_ASSERT(source->data.refcounted.length >= split);
 
     /* Build the result */
-    head.refcount = source->refcount->sub_refcount;
+    head.refcount = source->refcount->sub_refcount();
     /* Bump the refcount */
-    head.refcount->vtable->ref(head.refcount);
+    head.refcount->Ref();
     /* Point into the source array */
     head.data.refcounted.bytes = source->data.refcounted.bytes;
     head.data.refcounted.length = split;
-    source->refcount = source->refcount->sub_refcount;
+    source->refcount = source->refcount->sub_refcount();
     source->data.refcounted.bytes += split;
     source->data.refcounted.length -= split;
   }
@@ -421,8 +403,9 @@
 }
 
 int grpc_slice_eq(grpc_slice a, grpc_slice b) {
-  if (a.refcount && b.refcount && a.refcount->vtable == b.refcount->vtable) {
-    return a.refcount->vtable->eq(a, b);
+  if (a.refcount && b.refcount &&
+      a.refcount->GetType() == b.refcount->GetType()) {
+    return a.refcount->Eq(a, b);
   }
   return grpc_slice_default_eq_impl(a, b);
 }
diff --git a/src/core/lib/slice/slice_buffer.cc b/src/core/lib/slice/slice_buffer.cc
index 1f1c08b..111d3c9 100644
--- a/src/core/lib/slice/slice_buffer.cc
+++ b/src/core/lib/slice/slice_buffer.cc
@@ -33,6 +33,10 @@
 #define GROW(x) (3 * (x) / 2)
 
 static void maybe_embiggen(grpc_slice_buffer* sb) {
+  if (sb->count == 0) {
+    sb->slices = sb->base_slices;
+  }
+
   /* How far away from sb->base_slices is sb->slices pointer */
   size_t slice_offset = static_cast<size_t>(sb->slices - sb->base_slices);
   size_t slice_count = sb->count + slice_offset;
@@ -177,6 +181,7 @@
 
   sb->count = 0;
   sb->length = 0;
+  sb->slices = sb->base_slices;
 }
 
 void grpc_slice_buffer_reset_and_unref(grpc_slice_buffer* sb) {
diff --git a/src/core/lib/slice/slice_intern.cc b/src/core/lib/slice/slice_intern.cc
index 0eef38d..0f190c1 100644
--- a/src/core/lib/slice/slice_intern.cc
+++ b/src/core/lib/slice/slice_intern.cc
@@ -27,6 +27,7 @@
 #include <grpc/support/log.h>
 
 #include "src/core/lib/gpr/murmur_hash.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/iomgr_internal.h" /* for iomgr_abort_on_leaks() */
 #include "src/core/lib/profiling/timers.h"
 #include "src/core/lib/slice/slice_string_helpers.h"
@@ -39,24 +40,17 @@
 #define TABLE_IDX(hash, capacity) (((hash) >> LOG2_SHARD_COUNT) % (capacity))
 #define SHARD_IDX(hash) ((hash) & ((1 << LOG2_SHARD_COUNT) - 1))
 
-typedef struct interned_slice_refcount {
-  grpc_slice_refcount base;
-  grpc_slice_refcount sub;
-  size_t length;
-  gpr_atm refcnt;
-  uint32_t hash;
-  struct interned_slice_refcount* bucket_next;
-} interned_slice_refcount;
+using grpc_core::InternedSliceRefcount;
 
 typedef struct slice_shard {
   gpr_mu mu;
-  interned_slice_refcount** strs;
+  InternedSliceRefcount** strs;
   size_t count;
   size_t capacity;
 } slice_shard;
 
 /* hash seed: decided at initialization time */
-static uint32_t g_hash_seed;
+uint32_t g_hash_seed;
 static int g_forced_hash_seed = 0;
 
 static slice_shard g_shards[SHARD_COUNT];
@@ -69,73 +63,35 @@
 static static_metadata_hash_ent
     static_metadata_hash[4 * GRPC_STATIC_MDSTR_COUNT];
 static uint32_t max_static_metadata_hash_probe;
-static uint32_t static_metadata_hash_values[GRPC_STATIC_MDSTR_COUNT];
+uint32_t grpc_static_metadata_hash_values[GRPC_STATIC_MDSTR_COUNT];
 
-static void interned_slice_ref(void* p) {
-  interned_slice_refcount* s = static_cast<interned_slice_refcount*>(p);
-  GPR_ASSERT(gpr_atm_no_barrier_fetch_add(&s->refcnt, 1) > 0);
-}
+namespace grpc_core {
 
-static void interned_slice_destroy(interned_slice_refcount* s) {
-  slice_shard* shard = &g_shards[SHARD_IDX(s->hash)];
-  gpr_mu_lock(&shard->mu);
-  GPR_ASSERT(0 == gpr_atm_no_barrier_load(&s->refcnt));
-  interned_slice_refcount** prev_next;
-  interned_slice_refcount* cur;
-  for (prev_next = &shard->strs[TABLE_IDX(s->hash, shard->capacity)],
+InternedSliceRefcount::~InternedSliceRefcount() {
+  slice_shard* shard = &g_shards[SHARD_IDX(this->hash)];
+  MutexLock lock(&shard->mu);
+  InternedSliceRefcount** prev_next;
+  InternedSliceRefcount* cur;
+  for (prev_next = &shard->strs[TABLE_IDX(this->hash, shard->capacity)],
       cur = *prev_next;
-       cur != s; prev_next = &cur->bucket_next, cur = cur->bucket_next)
+       cur != this; prev_next = &cur->bucket_next, cur = cur->bucket_next)
     ;
   *prev_next = cur->bucket_next;
   shard->count--;
-  gpr_free(s);
-  gpr_mu_unlock(&shard->mu);
 }
 
-static void interned_slice_unref(void* p) {
-  interned_slice_refcount* s = static_cast<interned_slice_refcount*>(p);
-  if (1 == gpr_atm_full_fetch_add(&s->refcnt, -1)) {
-    interned_slice_destroy(s);
-  }
-}
-
-static void interned_slice_sub_ref(void* p) {
-  interned_slice_ref((static_cast<char*>(p)) -
-                     offsetof(interned_slice_refcount, sub));
-}
-
-static void interned_slice_sub_unref(void* p) {
-  interned_slice_unref((static_cast<char*>(p)) -
-                       offsetof(interned_slice_refcount, sub));
-}
-
-static uint32_t interned_slice_hash(grpc_slice slice) {
-  interned_slice_refcount* s =
-      reinterpret_cast<interned_slice_refcount*>(slice.refcount);
-  return s->hash;
-}
-
-static int interned_slice_eq(grpc_slice a, grpc_slice b) {
-  return a.refcount == b.refcount;
-}
-
-static const grpc_slice_refcount_vtable interned_slice_vtable = {
-    interned_slice_ref, interned_slice_unref, interned_slice_eq,
-    interned_slice_hash};
-static const grpc_slice_refcount_vtable interned_slice_sub_vtable = {
-    interned_slice_sub_ref, interned_slice_sub_unref,
-    grpc_slice_default_eq_impl, grpc_slice_default_hash_impl};
+}  // namespace grpc_core
 
 static void grow_shard(slice_shard* shard) {
   GPR_TIMER_SCOPE("grow_strtab", 0);
 
   size_t capacity = shard->capacity * 2;
   size_t i;
-  interned_slice_refcount** strtab;
-  interned_slice_refcount *s, *next;
+  InternedSliceRefcount** strtab;
+  InternedSliceRefcount *s, *next;
 
-  strtab = static_cast<interned_slice_refcount**>(
-      gpr_zalloc(sizeof(interned_slice_refcount*) * capacity));
+  strtab = static_cast<InternedSliceRefcount**>(
+      gpr_zalloc(sizeof(InternedSliceRefcount*) * capacity));
 
   for (i = 0; i < shard->capacity; i++) {
     for (s = shard->strs[i]; s; s = next) {
@@ -150,7 +106,7 @@
   shard->capacity = capacity;
 }
 
-static grpc_slice materialize(interned_slice_refcount* s) {
+static grpc_slice materialize(InternedSliceRefcount* s) {
   grpc_slice slice;
   slice.refcount = &s->base;
   slice.data.refcounted.bytes = reinterpret_cast<uint8_t*>(s + 1);
@@ -164,7 +120,7 @@
 }
 
 uint32_t grpc_static_slice_hash(grpc_slice s) {
-  return static_metadata_hash_values[GRPC_STATIC_METADATA_INDEX(s)];
+  return grpc_static_metadata_hash_values[GRPC_STATIC_METADATA_INDEX(s)];
 }
 
 int grpc_static_slice_eq(grpc_slice a, grpc_slice b) {
@@ -173,7 +129,7 @@
 
 uint32_t grpc_slice_hash(grpc_slice s) {
   return s.refcount == nullptr ? grpc_slice_default_hash_impl(s)
-                               : s.refcount->vtable->hash(s);
+                               : s.refcount->Hash(s);
 }
 
 grpc_slice grpc_slice_maybe_static_intern(grpc_slice slice,
@@ -197,8 +153,9 @@
 }
 
 bool grpc_slice_is_interned(const grpc_slice& slice) {
-  return (slice.refcount && slice.refcount->vtable == &interned_slice_vtable) ||
-         GRPC_IS_STATIC_METADATA_STRING(slice);
+  return (slice.refcount &&
+          (slice.refcount->GetType() == grpc_slice_refcount::Type::INTERNED ||
+           GRPC_IS_STATIC_METADATA_STRING(slice)));
 }
 
 grpc_slice grpc_slice_intern(grpc_slice slice) {
@@ -208,6 +165,7 @@
   }
 
   uint32_t hash = grpc_slice_hash(slice);
+
   for (uint32_t i = 0; i <= max_static_metadata_hash_probe; i++) {
     static_metadata_hash_ent ent =
         static_metadata_hash[(hash + i) % GPR_ARRAY_SIZE(static_metadata_hash)];
@@ -217,7 +175,7 @@
     }
   }
 
-  interned_slice_refcount* s;
+  InternedSliceRefcount* s;
   slice_shard* shard = &g_shards[SHARD_IDX(hash)];
 
   gpr_mu_lock(&shard->mu);
@@ -226,14 +184,7 @@
   size_t idx = TABLE_IDX(hash, shard->capacity);
   for (s = shard->strs[idx]; s; s = s->bucket_next) {
     if (s->hash == hash && grpc_slice_eq(slice, materialize(s))) {
-      if (gpr_atm_no_barrier_fetch_add(&s->refcnt, 1) == 0) {
-        /* If we get here, we've added a ref to something that was about to
-         * die - drop it immediately.
-         * The *only* possible path here (given the shard mutex) should be to
-         * drop from one ref back to zero - assert that with a CAS */
-        GPR_ASSERT(gpr_atm_rel_cas(&s->refcnt, 1, 0));
-        /* and treat this as if we were never here... sshhh */
-      } else {
+      if (s->refcnt.RefIfNonZero()) {
         gpr_mu_unlock(&shard->mu);
         return materialize(s);
       }
@@ -242,27 +193,20 @@
 
   /* not found: create a new string */
   /* string data goes after the internal_string header */
-  s = static_cast<interned_slice_refcount*>(
+  s = static_cast<InternedSliceRefcount*>(
       gpr_malloc(sizeof(*s) + GRPC_SLICE_LENGTH(slice)));
-  gpr_atm_rel_store(&s->refcnt, 1);
-  s->length = GRPC_SLICE_LENGTH(slice);
-  s->hash = hash;
-  s->base.vtable = &interned_slice_vtable;
-  s->base.sub_refcount = &s->sub;
-  s->sub.vtable = &interned_slice_sub_vtable;
-  s->sub.sub_refcount = &s->sub;
-  s->bucket_next = shard->strs[idx];
+
+  new (s) grpc_core::InternedSliceRefcount(GRPC_SLICE_LENGTH(slice), hash,
+                                           shard->strs[idx]);
+  memcpy(reinterpret_cast<char*>(s + 1), GRPC_SLICE_START_PTR(slice),
+         GRPC_SLICE_LENGTH(slice));
   shard->strs[idx] = s;
-  memcpy(s + 1, GRPC_SLICE_START_PTR(slice), GRPC_SLICE_LENGTH(slice));
-
   shard->count++;
-
   if (shard->count > shard->capacity * 2) {
     grow_shard(shard);
   }
 
   gpr_mu_unlock(&shard->mu);
-
   return materialize(s);
 }
 
@@ -280,7 +224,7 @@
     gpr_mu_init(&shard->mu);
     shard->count = 0;
     shard->capacity = INITIAL_SHARD_CAPACITY;
-    shard->strs = static_cast<interned_slice_refcount**>(
+    shard->strs = static_cast<InternedSliceRefcount**>(
         gpr_zalloc(sizeof(*shard->strs) * shard->capacity));
   }
   for (size_t i = 0; i < GPR_ARRAY_SIZE(static_metadata_hash); i++) {
@@ -289,13 +233,13 @@
   }
   max_static_metadata_hash_probe = 0;
   for (size_t i = 0; i < GRPC_STATIC_MDSTR_COUNT; i++) {
-    static_metadata_hash_values[i] =
+    grpc_static_metadata_hash_values[i] =
         grpc_slice_default_hash_impl(grpc_static_slice_table[i]);
     for (size_t j = 0; j < GPR_ARRAY_SIZE(static_metadata_hash); j++) {
-      size_t slot = (static_metadata_hash_values[i] + j) %
+      size_t slot = (grpc_static_metadata_hash_values[i] + j) %
                     GPR_ARRAY_SIZE(static_metadata_hash);
       if (static_metadata_hash[slot].idx == GRPC_STATIC_MDSTR_COUNT) {
-        static_metadata_hash[slot].hash = static_metadata_hash_values[i];
+        static_metadata_hash[slot].hash = grpc_static_metadata_hash_values[i];
         static_metadata_hash[slot].idx = static_cast<uint32_t>(i);
         if (j > max_static_metadata_hash_probe) {
           max_static_metadata_hash_probe = static_cast<uint32_t>(j);
@@ -315,8 +259,7 @@
       gpr_log(GPR_DEBUG, "WARNING: %" PRIuPTR " metadata strings were leaked",
               shard->count);
       for (size_t j = 0; j < shard->capacity; j++) {
-        for (interned_slice_refcount* s = shard->strs[j]; s;
-             s = s->bucket_next) {
+        for (InternedSliceRefcount* s = shard->strs[j]; s; s = s->bucket_next) {
           char* text =
               grpc_dump_slice(materialize(s), GPR_DUMP_HEX | GPR_DUMP_ASCII);
           gpr_log(GPR_DEBUG, "LEAKED: %s", text);
diff --git a/src/core/lib/slice/slice_internal.h b/src/core/lib/slice/slice_internal.h
index 0e50866..a9f6087 100644
--- a/src/core/lib/slice/slice_internal.h
+++ b/src/core/lib/slice/slice_internal.h
@@ -23,17 +23,215 @@
 
 #include <grpc/slice.h>
 #include <grpc/slice_buffer.h>
+#include <string.h>
+
+#include "src/core/lib/gpr/murmur_hash.h"
+#include "src/core/lib/gprpp/ref_counted.h"
+#include "src/core/lib/transport/static_metadata.h"
+
+// Interned slices have specific fast-path operations for hashing. To inline
+// these operations, we need to forward declare them here.
+extern uint32_t grpc_static_metadata_hash_values[GRPC_STATIC_MDSTR_COUNT];
+extern uint32_t g_hash_seed;
+
+// grpc_slice_refcount : A reference count for grpc_slice.
+//
+// Non-inlined grpc_slice objects are refcounted. Historically this was
+// implemented via grpc_slice_refcount, a C-style polymorphic class using a
+// manually managed vtable of operations. Subclasses would define their own
+// vtable; the 'virtual' methods (ref, unref, equals and hash) would simply call
+// the function pointers in the vtable as necessary.
+//
+// Unfortunately, this leads to some inefficiencies in the generated code that
+// can be improved upon. For example, equality checking for interned slices is a
+// simple equality check on the refcount pointer. With the vtable approach, this
+// would translate to roughly the following (high-level) instructions:
+//
+// grpc_slice_equals(slice1, slice2):
+//   load vtable->eq -> eq_func
+//   call eq_func(slice1, slice2)
+//
+// interned_slice_equals(slice1, slice2)
+//   load slice1.ref -> r1
+//   load slice2.ref -> r2
+//   cmp r1, r2 -> retval
+//   ret retval
+//
+// This leads to a function call for a function defined in another translation
+// unit, which imposes memory barriers, which reduces the compiler's ability to
+// optimize (in addition to the added overhead of call/ret). Additionally, it
+// may be harder to reason about branch prediction when we're jumping to
+// essentially arbitrarily provided function pointers.
+//
+// In addition, it is arguable that while virtualization was helpful for
+// Equals()/Hash() methods, that it was fundamentally unnecessary for
+// Ref()/Unref().
+//
+// Instead, grpc_slice_refcount provides the same functionality as the C-style
+// virtual class, but in a de-virtualized manner - Eq(), Hash(), Ref() and
+// Unref() are provided within this header file. Fastpaths for Eq()/Hash()
+// (interned and static metadata slices), as well as the Ref() operation, can
+// all be inlined without any memory barriers.
+//
+// It does this by:
+// 1. Using grpc_core::RefCount<> (header-only) for Ref/Unref. Two special cases
+//    need support: No-op ref/unref (eg. static metadata slices) and stream
+//    slice references (where all the slices share the streamref). This is in
+//    addition to the normal case of '1 slice, 1 ref'.
+//    To support these cases, we explicitly track a nullable pointer to the
+//    underlying RefCount<>. No-op ref/unref is used by checking the pointer for
+//    null, and doing nothing if it is. Both stream slice refs and 'normal'
+//    slices use the same path for Ref/Unref (by targeting the non-null
+//    pointer).
+//
+// 2. introducing the notion of grpc_slice_refcount::Type. This describes if a
+//    slice ref is used by a static metadata slice, an interned slice, or other
+//    slices. We switch on the slice ref type in order to provide fastpaths for
+//    Equals() and Hash().
+//
+// In total, this saves us roughly 1-2% latency for unary calls, with smaller
+// calls benefitting. The effect is present, but not as useful, for larger calls
+// where the cost of sending the data dominates.
+struct grpc_slice_refcount {
+ public:
+  enum class Type {
+    STATIC,    // Refcount for a static metadata slice.
+    INTERNED,  // Refcount for an interned slice.
+    REGULAR    // Refcount for non-static-metadata, non-interned slices.
+  };
+  typedef void (*DestroyerFn)(void*);
+
+  grpc_slice_refcount() = default;
+
+  explicit grpc_slice_refcount(grpc_slice_refcount* sub) : sub_refcount_(sub) {}
+  // Regular constructor for grpc_slice_refcount.
+  //
+  // Parameters:
+  //  1. grpc_slice_refcount::Type type
+  //  Whether we are the refcount for a static
+  //  metadata slice, an interned slice, or any other kind of slice.
+  //
+  //  2. RefCount* ref
+  //  The pointer to the actual underlying grpc_core::RefCount. Rather than
+  //  performing struct offset computations as in the original implementation to
+  //  get to the refcount, which requires a virtual method, we devirtualize by
+  //  using a nullable pointer to allow a single pair of Ref/Unref methods.
+  //
+  //  3. DestroyerFn destroyer_fn
+  //  Called when the refcount goes to 0, with destroyer_arg as parameter.
+  //
+  //  4. void* destroyer_arg
+  //  Argument for the virtualized destructor.
+  //
+  //  5. grpc_slice_refcount* sub
+  //  Argument used for interned slices.
+  grpc_slice_refcount(grpc_slice_refcount::Type type, grpc_core::RefCount* ref,
+                      DestroyerFn destroyer_fn, void* destroyer_arg,
+                      grpc_slice_refcount* sub)
+      : ref_(ref),
+        ref_type_(type),
+        sub_refcount_(sub),
+        dest_fn_(destroyer_fn),
+        destroy_fn_arg_(destroyer_arg) {}
+  // Initializer for static refcounts.
+  grpc_slice_refcount(grpc_slice_refcount* sub, Type type)
+      : ref_type_(type), sub_refcount_(sub) {}
+
+  Type GetType() const { return ref_type_; }
+
+  int Eq(const grpc_slice& a, const grpc_slice& b);
+
+  uint32_t Hash(const grpc_slice& slice);
+  void Ref() {
+    if (ref_ == nullptr) return;
+    ref_->RefNonZero();
+  }
+  void Unref() {
+    if (ref_ == nullptr) return;
+    if (ref_->Unref()) {
+      dest_fn_(destroy_fn_arg_);
+    }
+  }
+
+  grpc_slice_refcount* sub_refcount() const { return sub_refcount_; }
+
+ private:
+  grpc_core::RefCount* ref_ = nullptr;
+  const Type ref_type_ = Type::REGULAR;
+  grpc_slice_refcount* sub_refcount_ = this;
+  DestroyerFn dest_fn_ = nullptr;
+  void* destroy_fn_arg_ = nullptr;
+};
+
+namespace grpc_core {
+
+struct InternedSliceRefcount {
+  static void Destroy(void* arg) {
+    auto* rc = static_cast<InternedSliceRefcount*>(arg);
+    rc->~InternedSliceRefcount();
+    gpr_free(rc);
+  }
+
+  InternedSliceRefcount(size_t length, uint32_t hash,
+                        InternedSliceRefcount* bucket_next)
+      : base(grpc_slice_refcount::Type::INTERNED, &refcnt, Destroy, this, &sub),
+        sub(grpc_slice_refcount::Type::REGULAR, &refcnt, Destroy, this, &sub),
+        length(length),
+        hash(hash),
+        bucket_next(bucket_next) {}
+
+  ~InternedSliceRefcount();
+
+  grpc_slice_refcount base;
+  grpc_slice_refcount sub;
+  const size_t length;
+  RefCount refcnt;
+  const uint32_t hash;
+  InternedSliceRefcount* bucket_next;
+};
+
+}  // namespace grpc_core
+
+inline int grpc_slice_refcount::Eq(const grpc_slice& a, const grpc_slice& b) {
+  switch (ref_type_) {
+    case Type::STATIC:
+      return GRPC_STATIC_METADATA_INDEX(a) == GRPC_STATIC_METADATA_INDEX(b);
+    case Type::INTERNED:
+      return a.refcount == b.refcount;
+    case Type::REGULAR:
+      break;
+  }
+  if (GRPC_SLICE_LENGTH(a) != GRPC_SLICE_LENGTH(b)) return false;
+  if (GRPC_SLICE_LENGTH(a) == 0) return true;
+  return 0 == memcmp(GRPC_SLICE_START_PTR(a), GRPC_SLICE_START_PTR(b),
+                     GRPC_SLICE_LENGTH(a));
+}
+
+inline uint32_t grpc_slice_refcount::Hash(const grpc_slice& slice) {
+  switch (ref_type_) {
+    case Type::STATIC:
+      return ::grpc_static_metadata_hash_values[GRPC_STATIC_METADATA_INDEX(
+          slice)];
+    case Type::INTERNED:
+      return reinterpret_cast<grpc_core::InternedSliceRefcount*>(slice.refcount)
+          ->hash;
+    case Type::REGULAR:
+      break;
+  }
+  return gpr_murmur_hash3(GRPC_SLICE_START_PTR(slice), GRPC_SLICE_LENGTH(slice),
+                          g_hash_seed);
+}
 
 inline const grpc_slice& grpc_slice_ref_internal(const grpc_slice& slice) {
   if (slice.refcount) {
-    slice.refcount->vtable->ref(slice.refcount);
+    slice.refcount->Ref();
   }
   return slice;
 }
 
 inline void grpc_slice_unref_internal(const grpc_slice& slice) {
   if (slice.refcount) {
-    slice.refcount->vtable->unref(slice.refcount);
+    slice.refcount->Unref();
   }
 }
 
diff --git a/src/core/lib/surface/api_trace.h b/src/core/lib/surface/api_trace.h
index 72ed830..51d1f52 100644
--- a/src/core/lib/surface/api_trace.h
+++ b/src/core/lib/surface/api_trace.h
@@ -45,7 +45,7 @@
 /* Due to the limitations of C89's preprocessor, the arity of the var-arg list
    'nargs' must be specified. */
 #define GRPC_API_TRACE(fmt, nargs, args)                      \
-  if (grpc_api_trace.enabled()) {                             \
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace)) {              \
     gpr_log(GPR_INFO, fmt GRPC_API_TRACE_UNWRAP##nargs args); \
   }
 
diff --git a/src/core/lib/surface/call.cc b/src/core/lib/surface/call.cc
index 8aaff4a..275111f3 100644
--- a/src/core/lib/surface/call.cc
+++ b/src/core/lib/surface/call.cc
@@ -35,10 +35,11 @@
 #include "src/core/lib/compression/algorithm_metadata.h"
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/gpr/alloc.h"
-#include "src/core/lib/gpr/arena.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/arena.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
+#include "src/core/lib/gprpp/ref_counted.h"
 #include "src/core/lib/iomgr/timer.h"
 #include "src/core/lib/profiling/timers.h"
 #include "src/core/lib/slice/slice_internal.h"
@@ -124,14 +125,12 @@
 #define RECV_INITIAL_METADATA_FIRST ((gpr_atm)1)
 
 struct grpc_call {
-  grpc_call(gpr_arena* arena, const grpc_call_create_args& args)
+  grpc_call(grpc_core::Arena* arena, const grpc_call_create_args& args)
       : arena(arena),
         cq(args.cq),
         channel(args.channel),
         is_client(args.server_transport_data == nullptr),
         stream_op_payload(context) {
-    gpr_ref_init(&ext_ref, 1);
-    grpc_call_combiner_init(&call_combiner);
     for (int i = 0; i < 2; i++) {
       for (int j = 0; j < 2; j++) {
         metadata_batch[i][j].deadline = GRPC_MILLIS_INF_FUTURE;
@@ -141,12 +140,11 @@
 
   ~grpc_call() {
     gpr_free(static_cast<void*>(const_cast<char*>(final_info.error_string)));
-    grpc_call_combiner_destroy(&call_combiner);
   }
 
-  gpr_refcount ext_ref;
-  gpr_arena* arena;
-  grpc_call_combiner call_combiner;
+  grpc_core::RefCount ext_ref;
+  grpc_core::Arena* arena;
+  grpc_core::CallCombiner call_combiner;
   grpc_completion_queue* cq;
   grpc_polling_entity pollent;
   grpc_channel* channel;
@@ -292,13 +290,13 @@
 }
 
 void* grpc_call_arena_alloc(grpc_call* call, size_t size) {
-  return gpr_arena_alloc(call->arena, size);
+  return call->arena->Alloc(size);
 }
 
 static parent_call* get_or_create_parent_call(grpc_call* call) {
   parent_call* p = (parent_call*)gpr_atm_acq_load(&call->parent_call_atm);
   if (p == nullptr) {
-    p = new (gpr_arena_alloc(call->arena, sizeof(*p))) parent_call();
+    p = call->arena->New<parent_call>();
     if (!gpr_atm_rel_cas(&call->parent_call_atm, (gpr_atm) nullptr,
                          (gpr_atm)p)) {
       p->~parent_call();
@@ -323,16 +321,23 @@
 
   GRPC_CHANNEL_INTERNAL_REF(args->channel, "call");
 
+  grpc_core::Arena* arena;
+  grpc_call* call;
   grpc_error* error = GRPC_ERROR_NONE;
   grpc_channel_stack* channel_stack =
       grpc_channel_get_channel_stack(args->channel);
-  grpc_call* call;
   size_t initial_size = grpc_channel_get_call_size_estimate(args->channel);
   GRPC_STATS_INC_CALL_INITIAL_SIZE(initial_size);
-  gpr_arena* arena = gpr_arena_create(initial_size);
-  call = new (gpr_arena_alloc(
-      arena, GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call)) +
-                 channel_stack->call_stack_size)) grpc_call(arena, *args);
+  size_t call_and_stack_size =
+      GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call)) +
+      channel_stack->call_stack_size;
+  size_t call_alloc_size =
+      call_and_stack_size + (args->parent ? sizeof(child_call) : 0);
+
+  std::pair<grpc_core::Arena*, void*> arena_with_call =
+      grpc_core::Arena::CreateWithAlloc(initial_size, call_alloc_size);
+  arena = arena_with_call.first;
+  call = new (arena_with_call.second) grpc_call(arena, *args);
   *out_call = call;
   grpc_slice path = grpc_empty_slice();
   if (call->is_client) {
@@ -364,8 +369,8 @@
   bool immediately_cancel = false;
 
   if (args->parent != nullptr) {
-    call->child = new (gpr_arena_alloc(arena, sizeof(child_call)))
-        child_call(args->parent);
+    call->child = new (reinterpret_cast<char*>(arena_with_call.second) +
+                       call_and_stack_size) child_call(args->parent);
 
     GRPC_CALL_INTERNAL_REF(args->parent, "child");
     GPR_ASSERT(call->is_client);
@@ -502,9 +507,9 @@
 static void release_call(void* call, grpc_error* error) {
   grpc_call* c = static_cast<grpc_call*>(call);
   grpc_channel* channel = c->channel;
-  gpr_arena* arena = c->arena;
+  grpc_core::Arena* arena = c->arena;
   c->~grpc_call();
-  grpc_channel_update_call_size_estimate(channel, gpr_arena_destroy(arena));
+  grpc_channel_update_call_size_estimate(channel, arena->Destroy());
   GRPC_CHANNEL_INTERNAL_UNREF(channel, "call");
 }
 
@@ -548,10 +553,10 @@
                                             grpc_schedule_on_exec_ctx));
 }
 
-void grpc_call_ref(grpc_call* c) { gpr_ref(&c->ext_ref); }
+void grpc_call_ref(grpc_call* c) { c->ext_ref.Ref(); }
 
 void grpc_call_unref(grpc_call* c) {
-  if (!gpr_unref(&c->ext_ref)) return;
+  if (GPR_LIKELY(!c->ext_ref.Unref())) return;
 
   GPR_TIMER_SCOPE("grpc_call_unref", 0);
 
@@ -589,7 +594,7 @@
     // holding to the call stack. Also flush the closures on exec_ctx so that
     // filters that schedule cancel notification closures on exec_ctx do not
     // need to take a ref of the call stack to guarantee closure liveness.
-    grpc_call_combiner_set_notify_on_cancel(&c->call_combiner, nullptr);
+    c->call_combiner.SetNotifyOnCancel(nullptr);
     grpc_core::ExecCtx::Get()->Flush();
   }
   GRPC_CALL_INTERNAL_UNREF(c, "destroy");
@@ -685,7 +690,7 @@
   // any in-flight asynchronous actions that may be holding the call
   // combiner.  This ensures that the cancel_stream batch can be sent
   // down the filter stack in a timely manner.
-  grpc_call_combiner_cancel(&c->call_combiner, GRPC_ERROR_REF(error));
+  c->call_combiner.Cancel(GRPC_ERROR_REF(error));
   cancel_state* state = static_cast<cancel_state*>(gpr_malloc(sizeof(*state)));
   state->call = c;
   GRPC_CLOSURE_INIT(&state->finish_batch, done_termination, state,
@@ -718,7 +723,7 @@
 }
 
 static void set_final_status(grpc_call* call, grpc_error* error) {
-  if (grpc_call_error_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_call_error_trace)) {
     gpr_log(GPR_DEBUG, "set_final_status %s", call->is_client ? "CLI" : "SVR");
     gpr_log(GPR_DEBUG, "%s", grpc_error_string(error));
   }
@@ -1069,7 +1074,7 @@
   publish_app_metadata(call, b, true);
 }
 
-gpr_arena* grpc_call_get_arena(grpc_call* call) { return call->arena; }
+grpc_core::Arena* grpc_call_get_arena(grpc_call* call) { return call->arena; }
 
 grpc_call_stack* grpc_call_get_call_stack(grpc_call* call) {
   return CALL_STACK_FROM_CALL(call);
@@ -1130,8 +1135,7 @@
     bctl->~batch_control();
     bctl->op = {};
   } else {
-    bctl = new (gpr_arena_alloc(call->arena, sizeof(batch_control)))
-        batch_control();
+    bctl = call->arena->New<batch_control>();
     *pslot = bctl;
   }
   bctl->call = call;
@@ -1221,7 +1225,7 @@
 }
 
 static void finish_batch_step(batch_control* bctl) {
-  if (gpr_unref(&bctl->steps_to_complete)) {
+  if (GPR_UNLIKELY(gpr_unref(&bctl->steps_to_complete))) {
     post_batch_completion(bctl);
   }
 }
@@ -1276,7 +1280,7 @@
   }
 
   if (error != GRPC_ERROR_NONE) {
-    if (grpc_trace_operation_failures.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures)) {
       GRPC_LOG_IF_ERROR("receiving_slice_ready", GRPC_ERROR_REF(error));
     }
     call->receiving_stream.reset();
@@ -1400,7 +1404,7 @@
 
     GPR_ASSERT(call->encodings_accepted_by_peer != 0);
     if (!GPR_BITGET(call->encodings_accepted_by_peer, compression_algorithm)) {
-      if (grpc_compression_trace.enabled()) {
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_compression_trace)) {
         const char* algo_name = nullptr;
         grpc_compression_algorithm_name(compression_algorithm, &algo_name);
         gpr_log(GPR_ERROR,
@@ -1559,7 +1563,10 @@
           goto done_with_error;
         }
         /* process compression level */
-        memset(&call->compression_md, 0, sizeof(call->compression_md));
+        grpc_metadata& compression_md = call->compression_md;
+        compression_md.key = grpc_empty_slice();
+        compression_md.value = grpc_empty_slice();
+        compression_md.flags = 0;
         size_t additional_metadata_count = 0;
         grpc_compression_level effective_compression_level =
             GRPC_COMPRESS_LEVEL_NONE;
@@ -1582,8 +1589,8 @@
                   call, effective_compression_level);
           /* the following will be picked up by the compress filter and used
            * as the call's compression algorithm. */
-          call->compression_md.key = GRPC_MDSTR_GRPC_INTERNAL_ENCODING_REQUEST;
-          call->compression_md.value = grpc_compression_algorithm_slice(calgo);
+          compression_md.key = GRPC_MDSTR_GRPC_INTERNAL_ENCODING_REQUEST;
+          compression_md.value = grpc_compression_algorithm_slice(calgo);
           additional_metadata_count++;
         }
 
@@ -1597,8 +1604,7 @@
         if (!prepare_application_metadata(
                 call, static_cast<int>(op->data.send_initial_metadata.count),
                 op->data.send_initial_metadata.metadata, 0, call->is_client,
-                &call->compression_md,
-                static_cast<int>(additional_metadata_count))) {
+                &compression_md, static_cast<int>(additional_metadata_count))) {
           error = GRPC_CALL_ERROR_INVALID_METADATA;
           goto done_with_error;
         }
diff --git a/src/core/lib/surface/call.h b/src/core/lib/surface/call.h
index bd7295f..15392fe 100644
--- a/src/core/lib/surface/call.h
+++ b/src/core/lib/surface/call.h
@@ -23,6 +23,7 @@
 
 #include "src/core/lib/channel/channel_stack.h"
 #include "src/core/lib/channel/context.h"
+#include "src/core/lib/gprpp/arena.h"
 #include "src/core/lib/surface/api_trace.h"
 
 #include <grpc/grpc.h>
@@ -72,7 +73,7 @@
 #define GRPC_CALL_INTERNAL_UNREF(call, reason) grpc_call_internal_unref(call)
 #endif
 
-gpr_arena* grpc_call_get_arena(grpc_call* call);
+grpc_core::Arena* grpc_call_get_arena(grpc_call* call);
 
 grpc_call_stack* grpc_call_get_call_stack(grpc_call* call);
 
@@ -101,7 +102,11 @@
 void* grpc_call_context_get(grpc_call* call, grpc_context_index elem);
 
 #define GRPC_CALL_LOG_BATCH(sev, call, ops, nops, tag) \
-  if (grpc_api_trace.enabled()) grpc_call_log_batch(sev, call, ops, nops, tag)
+  do {                                                 \
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace)) {     \
+      grpc_call_log_batch(sev, call, ops, nops, tag);  \
+    }                                                  \
+  } while (0)
 
 uint8_t grpc_call_is_client(grpc_call* call);
 
diff --git a/src/core/lib/surface/call_details.cc b/src/core/lib/surface/call_details.cc
index 7f20b1d..55e9e34 100644
--- a/src/core/lib/surface/call_details.cc
+++ b/src/core/lib/surface/call_details.cc
@@ -29,7 +29,6 @@
 
 void grpc_call_details_init(grpc_call_details* cd) {
   GRPC_API_TRACE("grpc_call_details_init(cd=%p)", 1, (cd));
-  memset(cd, 0, sizeof(*cd));
   cd->method = grpc_empty_slice();
   cd->host = grpc_empty_slice();
 }
diff --git a/src/core/lib/surface/completion_queue.cc b/src/core/lib/surface/completion_queue.cc
index 7d67920..e796071 100644
--- a/src/core/lib/surface/completion_queue.cc
+++ b/src/core/lib/surface/completion_queue.cc
@@ -411,13 +411,16 @@
 
 grpc_core::TraceFlag grpc_cq_pluck_trace(false, "queue_pluck");
 
-#define GRPC_SURFACE_TRACE_RETURNED_EVENT(cq, event)                       \
-  if (grpc_api_trace.enabled() && (grpc_cq_pluck_trace.enabled() ||        \
-                                   (event)->type != GRPC_QUEUE_TIMEOUT)) { \
-    char* _ev = grpc_event_string(event);                                  \
-    gpr_log(GPR_INFO, "RETURN_EVENT[%p]: %s", cq, _ev);                    \
-    gpr_free(_ev);                                                         \
-  }
+#define GRPC_SURFACE_TRACE_RETURNED_EVENT(cq, event)      \
+  do {                                                    \
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) &&        \
+        (GRPC_TRACE_FLAG_ENABLED(grpc_cq_pluck_trace) ||  \
+         (event)->type != GRPC_QUEUE_TIMEOUT)) {          \
+      char* _ev = grpc_event_string(event);               \
+      gpr_log(GPR_INFO, "RETURN_EVENT[%p]: %s", cq, _ev); \
+      gpr_free(_ev);                                      \
+    }                                                     \
+  } while (0)
 
 static void on_pollset_shutdown_done(void* cq, grpc_error* error);
 
@@ -572,7 +575,7 @@
 #ifndef NDEBUG
 void grpc_cq_internal_ref(grpc_completion_queue* cq, const char* reason,
                           const char* file, int line) {
-  if (grpc_trace_cq_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_cq_refcount)) {
     gpr_atm val = gpr_atm_no_barrier_load(&cq->owning_refs.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
             "CQ:%p   ref %" PRIdPTR " -> %" PRIdPTR " %s", cq, val, val + 1,
@@ -592,7 +595,7 @@
 #ifndef NDEBUG
 void grpc_cq_internal_unref(grpc_completion_queue* cq, const char* reason,
                             const char* file, int line) {
-  if (grpc_trace_cq_refcount.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_cq_refcount)) {
     gpr_atm val = gpr_atm_no_barrier_load(&cq->owning_refs.count);
     gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
             "CQ:%p unref %" PRIdPTR " -> %" PRIdPTR " %s", cq, val, val - 1,
@@ -678,14 +681,16 @@
                                void* done_arg, grpc_cq_completion* storage) {
   GPR_TIMER_SCOPE("cq_end_op_for_next", 0);
 
-  if (grpc_api_trace.enabled() ||
-      (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE)) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) ||
+      (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
+       error != GRPC_ERROR_NONE)) {
     const char* errmsg = grpc_error_string(error);
     GRPC_API_TRACE(
         "cq_end_op_for_next(cq=%p, tag=%p, error=%s, "
         "done=%p, done_arg=%p, storage=%p)",
         6, (cq, tag, errmsg, done, done_arg, storage));
-    if (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
+        error != GRPC_ERROR_NONE) {
       gpr_log(GPR_ERROR, "Operation failed: tag=%p, error=%s", tag, errmsg);
     }
   }
@@ -759,14 +764,16 @@
   cq_pluck_data* cqd = static_cast<cq_pluck_data*> DATA_FROM_CQ(cq);
   int is_success = (error == GRPC_ERROR_NONE);
 
-  if (grpc_api_trace.enabled() ||
-      (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE)) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) ||
+      (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
+       error != GRPC_ERROR_NONE)) {
     const char* errmsg = grpc_error_string(error);
     GRPC_API_TRACE(
         "cq_end_op_for_pluck(cq=%p, tag=%p, error=%s, "
         "done=%p, done_arg=%p, storage=%p)",
         6, (cq, tag, errmsg, done, done_arg, storage));
-    if (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
+        error != GRPC_ERROR_NONE) {
       gpr_log(GPR_ERROR, "Operation failed: tag=%p, error=%s", tag, errmsg);
     }
   }
@@ -824,14 +831,16 @@
   cq_callback_data* cqd = static_cast<cq_callback_data*> DATA_FROM_CQ(cq);
   bool is_success = (error == GRPC_ERROR_NONE);
 
-  if (grpc_api_trace.enabled() ||
-      (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE)) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) ||
+      (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
+       error != GRPC_ERROR_NONE)) {
     const char* errmsg = grpc_error_string(error);
     GRPC_API_TRACE(
         "cq_end_op_for_callback(cq=%p, tag=%p, error=%s, "
         "done=%p, done_arg=%p, storage=%p)",
         6, (cq, tag, errmsg, done, done_arg, storage));
-    if (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
+        error != GRPC_ERROR_NONE) {
       gpr_log(GPR_ERROR, "Operation failed: tag=%p, error=%s", tag, errmsg);
     }
   }
@@ -906,7 +915,7 @@
 
 #ifndef NDEBUG
 static void dump_pending_tags(grpc_completion_queue* cq) {
-  if (!grpc_trace_pending_tags.enabled()) return;
+  if (!GRPC_TRACE_FLAG_ENABLED(grpc_trace_pending_tags)) return;
 
   gpr_strvec v;
   gpr_strvec_init(&v);
@@ -1002,15 +1011,15 @@
         continue;
       }
 
-      memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_SHUTDOWN;
+      ret.success = 0;
       break;
     }
 
     if (!is_finished_arg.first_loop &&
         grpc_core::ExecCtx::Get()->Now() >= deadline_millis) {
-      memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_TIMEOUT;
+      ret.success = 0;
       dump_pending_tags(cq);
       break;
     }
@@ -1027,8 +1036,8 @@
       gpr_log(GPR_ERROR, "Completion queue next failed: %s", msg);
 
       GRPC_ERROR_UNREF(err);
-      memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_TIMEOUT;
+      ret.success = 0;
       dump_pending_tags(cq);
       break;
     }
@@ -1176,7 +1185,7 @@
   grpc_pollset_worker* worker = nullptr;
   cq_pluck_data* cqd = static_cast<cq_pluck_data*> DATA_FROM_CQ(cq);
 
-  if (grpc_cq_pluck_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_cq_pluck_trace)) {
     GRPC_API_TRACE(
         "grpc_completion_queue_pluck("
         "cq=%p, tag=%p, "
@@ -1234,8 +1243,8 @@
     }
     if (cqd->shutdown.Load(grpc_core::MemoryOrder::RELAXED)) {
       gpr_mu_unlock(cq->mu);
-      memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_SHUTDOWN;
+      ret.success = 0;
       break;
     }
     if (!add_plucker(cq, tag, &worker)) {
@@ -1244,9 +1253,9 @@
               "is %d",
               GRPC_MAX_COMPLETION_QUEUE_PLUCKERS);
       gpr_mu_unlock(cq->mu);
-      memset(&ret, 0, sizeof(ret));
       /* TODO(ctiller): should we use a different result here */
       ret.type = GRPC_QUEUE_TIMEOUT;
+      ret.success = 0;
       dump_pending_tags(cq);
       break;
     }
@@ -1254,8 +1263,8 @@
         grpc_core::ExecCtx::Get()->Now() >= deadline_millis) {
       del_plucker(cq, tag, &worker);
       gpr_mu_unlock(cq->mu);
-      memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_TIMEOUT;
+      ret.success = 0;
       dump_pending_tags(cq);
       break;
     }
@@ -1269,8 +1278,8 @@
       gpr_log(GPR_ERROR, "Completion queue pluck failed: %s", msg);
 
       GRPC_ERROR_UNREF(err);
-      memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_TIMEOUT;
+      ret.success = 0;
       dump_pending_tags(cq);
       break;
     }
diff --git a/src/core/lib/surface/init.cc b/src/core/lib/surface/init.cc
index fdb584d..2a6d307 100644
--- a/src/core/lib/surface/init.cc
+++ b/src/core/lib/surface/init.cc
@@ -33,7 +33,7 @@
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/debug/trace.h"
 #include "src/core/lib/gprpp/fork.h"
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/http/parser.h"
 #include "src/core/lib/iomgr/call_combiner.h"
 #include "src/core/lib/iomgr/combiner.h"
@@ -73,6 +73,7 @@
   g_shutting_down = false;
   grpc_register_built_in_plugins();
   grpc_cq_global_init();
+  gpr_time_init();
   g_initializations = 0;
 }
 
@@ -132,8 +133,6 @@
     }
     grpc_core::Fork::GlobalInit();
     grpc_fork_handlers_auto_register();
-    gpr_time_init();
-    gpr_arena_init();
     grpc_stats_init();
     grpc_slice_intern_init();
     grpc_mdctx_global_init();
@@ -155,7 +154,7 @@
      * at the appropriate time */
     grpc_register_security_filters();
     register_builtin_channel_init();
-    grpc_tracer_init("GRPC_TRACE");
+    grpc_tracer_init();
     /* no more changes to channel init pipelines */
     grpc_channel_init_finalize();
     grpc_iomgr_start();
diff --git a/src/core/lib/surface/lame_client.cc b/src/core/lib/surface/lame_client.cc
index 5f5f10d..dde39b8 100644
--- a/src/core/lib/surface/lame_client.cc
+++ b/src/core/lib/surface/lame_client.cc
@@ -39,7 +39,7 @@
 namespace {
 
 struct CallData {
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
   grpc_linked_mdelem status;
   grpc_linked_mdelem details;
   grpc_core::Atomic<bool> filled_metadata;
diff --git a/src/core/lib/surface/server.cc b/src/core/lib/surface/server.cc
index 1e0ac0e..2377c4d 100644
--- a/src/core/lib/surface/server.cc
+++ b/src/core/lib/surface/server.cc
@@ -123,7 +123,7 @@
 typedef enum {
   /* waiting for metadata */
   NOT_STARTED,
-  /* inital metadata read, not flow controlled in yet */
+  /* initial metadata read, not flow controlled in yet */
   PENDING,
   /* flow controlled in, on completion queue */
   ACTIVATED,
@@ -190,7 +190,7 @@
   grpc_closure publish;
 
   call_data* pending_next = nullptr;
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
 };
 
 struct request_matcher {
@@ -347,8 +347,8 @@
  */
 
 static void request_matcher_init(request_matcher* rm, grpc_server* server) {
-  memset(rm, 0, sizeof(*rm));
   rm->server = server;
+  rm->pending_head = rm->pending_tail = nullptr;
   rm->requests_per_cq = static_cast<gpr_locked_mpscq*>(
       gpr_malloc(sizeof(*rm->requests_per_cq) * server->cq_count));
   for (size_t i = 0; i < server->cq_count; i++) {
@@ -464,7 +464,8 @@
   GRPC_CLOSURE_INIT(&chand->finish_destroy_channel_closure,
                     finish_destroy_channel, chand, grpc_schedule_on_exec_ctx);
 
-  if (grpc_server_channel_trace.enabled() && error != GRPC_ERROR_NONE) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_server_channel_trace) &&
+      error != GRPC_ERROR_NONE) {
     const char* msg = grpc_error_string(error);
     gpr_log(GPR_INFO, "Disconnected client: %s", msg);
   }
@@ -601,8 +602,9 @@
       break;
     case GRPC_SRM_PAYLOAD_READ_INITIAL_BYTE_BUFFER: {
       grpc_op op;
-      memset(&op, 0, sizeof(op));
       op.op = GRPC_OP_RECV_MESSAGE;
+      op.flags = 0;
+      op.reserved = nullptr;
       op.data.recv_message.recv_message = &calld->payload;
       GRPC_CLOSURE_INIT(&calld->publish, publish_new_rpc, elem,
                         grpc_schedule_on_exec_ctx);
@@ -1098,20 +1100,6 @@
   return m;
 }
 
-static void start_listeners(void* s, grpc_error* error) {
-  grpc_server* server = static_cast<grpc_server*>(s);
-  for (listener* l = server->listeners; l; l = l->next) {
-    l->start(server, l->arg, server->pollsets, server->pollset_count);
-  }
-
-  gpr_mu_lock(&server->mu_global);
-  server->starting = false;
-  gpr_cv_signal(&server->starting_cv);
-  gpr_mu_unlock(&server->mu_global);
-
-  server_unref(server);
-}
-
 void grpc_server_start(grpc_server* server) {
   size_t i;
   grpc_core::ExecCtx exec_ctx;
@@ -1133,13 +1121,18 @@
     request_matcher_init(&rm->matcher, server);
   }
 
-  server_ref(server);
+  gpr_mu_lock(&server->mu_global);
   server->starting = true;
-  GRPC_CLOSURE_SCHED(
-      GRPC_CLOSURE_CREATE(
-          start_listeners, server,
-          grpc_core::Executor::Scheduler(grpc_core::ExecutorJobType::SHORT)),
-      GRPC_ERROR_NONE);
+  gpr_mu_unlock(&server->mu_global);
+
+  for (listener* l = server->listeners; l; l = l->next) {
+    l->start(server, l->arg, server->pollsets, server->pollset_count);
+  }
+
+  gpr_mu_lock(&server->mu_global);
+  server->starting = false;
+  gpr_cv_signal(&server->starting_cv);
+  gpr_mu_unlock(&server->mu_global);
 }
 
 void grpc_server_get_pollsets(grpc_server* server, grpc_pollset*** pollsets,
diff --git a/src/core/lib/surface/version.cc b/src/core/lib/surface/version.cc
index bf2e6c9..8aeadaf5 100644
--- a/src/core/lib/surface/version.cc
+++ b/src/core/lib/surface/version.cc
@@ -25,4 +25,4 @@
 
 const char* grpc_version_string(void) { return "7.0.0"; }
 
-const char* grpc_g_stands_for(void) { return "godric"; }
+const char* grpc_g_stands_for(void) { return "gandalf"; }
diff --git a/src/core/lib/transport/bdp_estimator.cc b/src/core/lib/transport/bdp_estimator.cc
index 8e71f86..8835e32 100644
--- a/src/core/lib/transport/bdp_estimator.cc
+++ b/src/core/lib/transport/bdp_estimator.cc
@@ -46,7 +46,7 @@
               1e-9 * static_cast<double>(dt_ts.tv_nsec);
   double bw = dt > 0 ? (static_cast<double>(accumulator_) / dt) : 0;
   int start_inter_ping_delay = inter_ping_delay_;
-  if (grpc_bdp_estimator_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
     gpr_log(GPR_INFO,
             "bdp[%s]:complete acc=%" PRId64 " est=%" PRId64
             " dt=%lf bw=%lfMbs bw_est=%lfMbs",
@@ -57,7 +57,7 @@
   if (accumulator_ > 2 * estimate_ / 3 && bw > bw_est_) {
     estimate_ = GPR_MAX(accumulator_, estimate_ * 2);
     bw_est_ = bw;
-    if (grpc_bdp_estimator_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
       gpr_log(GPR_INFO, "bdp[%s]: estimate increased to %" PRId64, name_,
               estimate_);
     }
@@ -74,7 +74,7 @@
   }
   if (start_inter_ping_delay != inter_ping_delay_) {
     stable_estimate_count_ = 0;
-    if (grpc_bdp_estimator_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
       gpr_log(GPR_INFO, "bdp[%s]:update_inter_time to %dms", name_,
               inter_ping_delay_);
     }
diff --git a/src/core/lib/transport/bdp_estimator.h b/src/core/lib/transport/bdp_estimator.h
index ab13ae4..6dc4d6b 100644
--- a/src/core/lib/transport/bdp_estimator.h
+++ b/src/core/lib/transport/bdp_estimator.h
@@ -49,7 +49,7 @@
   // grpc_bdp_estimator_add_incoming_bytes once a ping has been scheduled by a
   // transport (but not necessarily started)
   void SchedulePing() {
-    if (grpc_bdp_estimator_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
       gpr_log(GPR_INFO, "bdp[%s]:sched acc=%" PRId64 " est=%" PRId64, name_,
               accumulator_, estimate_);
     }
@@ -62,7 +62,7 @@
   // once
   // the ping is on the wire
   void StartPing() {
-    if (grpc_bdp_estimator_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_bdp_estimator_trace)) {
       gpr_log(GPR_INFO, "bdp[%s]:start acc=%" PRId64 " est=%" PRId64, name_,
               accumulator_, estimate_);
     }
diff --git a/src/core/lib/transport/connectivity_state.cc b/src/core/lib/transport/connectivity_state.cc
index db6b6c0..bf35fd0 100644
--- a/src/core/lib/transport/connectivity_state.cc
+++ b/src/core/lib/transport/connectivity_state.cc
@@ -48,7 +48,6 @@
                                   grpc_connectivity_state init_state,
                                   const char* name) {
   gpr_atm_no_barrier_store(&tracker->current_state_atm, init_state);
-  tracker->current_error = GRPC_ERROR_NONE;
   tracker->watchers = nullptr;
   tracker->name = gpr_strdup(name);
 }
@@ -69,7 +68,6 @@
     GRPC_CLOSURE_SCHED(w->notify, error);
     gpr_free(w);
   }
-  GRPC_ERROR_UNREF(tracker->current_error);
   gpr_free(tracker->name);
 }
 
@@ -77,27 +75,13 @@
     grpc_connectivity_state_tracker* tracker) {
   grpc_connectivity_state cur = static_cast<grpc_connectivity_state>(
       gpr_atm_no_barrier_load(&tracker->current_state_atm));
-  if (grpc_connectivity_state_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_connectivity_state_trace)) {
     gpr_log(GPR_INFO, "CONWATCH: %p %s: get %s", tracker, tracker->name,
             grpc_connectivity_state_name(cur));
   }
   return cur;
 }
 
-grpc_connectivity_state grpc_connectivity_state_get(
-    grpc_connectivity_state_tracker* tracker, grpc_error** error) {
-  grpc_connectivity_state cur = static_cast<grpc_connectivity_state>(
-      gpr_atm_no_barrier_load(&tracker->current_state_atm));
-  if (grpc_connectivity_state_trace.enabled()) {
-    gpr_log(GPR_INFO, "CONWATCH: %p %s: get %s", tracker, tracker->name,
-            grpc_connectivity_state_name(cur));
-  }
-  if (error != nullptr) {
-    *error = GRPC_ERROR_REF(tracker->current_error);
-  }
-  return cur;
-}
-
 bool grpc_connectivity_state_has_watchers(
     grpc_connectivity_state_tracker* connectivity_state) {
   return connectivity_state->watchers != nullptr;
@@ -108,7 +92,7 @@
     grpc_closure* notify) {
   grpc_connectivity_state cur = static_cast<grpc_connectivity_state>(
       gpr_atm_no_barrier_load(&tracker->current_state_atm));
-  if (grpc_connectivity_state_trace.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_connectivity_state_trace)) {
     if (current == nullptr) {
       gpr_log(GPR_INFO, "CONWATCH: %p %s: unsubscribe notify=%p", tracker,
               tracker->name, notify);
@@ -140,7 +124,7 @@
   } else {
     if (cur != *current) {
       *current = cur;
-      GRPC_CLOSURE_SCHED(notify, GRPC_ERROR_REF(tracker->current_error));
+      GRPC_CLOSURE_SCHED(notify, GRPC_ERROR_NONE);
     } else {
       grpc_connectivity_state_watcher* w =
           static_cast<grpc_connectivity_state_watcher*>(gpr_malloc(sizeof(*w)));
@@ -155,29 +139,15 @@
 
 void grpc_connectivity_state_set(grpc_connectivity_state_tracker* tracker,
                                  grpc_connectivity_state state,
-                                 grpc_error* error, const char* reason) {
+                                 const char* reason) {
   grpc_connectivity_state cur = static_cast<grpc_connectivity_state>(
       gpr_atm_no_barrier_load(&tracker->current_state_atm));
   grpc_connectivity_state_watcher* w;
-  if (grpc_connectivity_state_trace.enabled()) {
-    const char* error_string = grpc_error_string(error);
-    gpr_log(GPR_INFO, "SET: %p %s: %s --> %s [%s] error=%p %s", tracker,
-            tracker->name, grpc_connectivity_state_name(cur),
-            grpc_connectivity_state_name(state), reason, error, error_string);
+  if (GRPC_TRACE_FLAG_ENABLED(grpc_connectivity_state_trace)) {
+    gpr_log(GPR_INFO, "SET: %p %s: %s --> %s [%s]", tracker, tracker->name,
+            grpc_connectivity_state_name(cur),
+            grpc_connectivity_state_name(state), reason);
   }
-  switch (state) {
-    case GRPC_CHANNEL_CONNECTING:
-    case GRPC_CHANNEL_IDLE:
-    case GRPC_CHANNEL_READY:
-      GPR_ASSERT(error == GRPC_ERROR_NONE);
-      break;
-    case GRPC_CHANNEL_SHUTDOWN:
-    case GRPC_CHANNEL_TRANSIENT_FAILURE:
-      GPR_ASSERT(error != GRPC_ERROR_NONE);
-      break;
-  }
-  GRPC_ERROR_UNREF(tracker->current_error);
-  tracker->current_error = error;
   if (cur == state) {
     return;
   }
@@ -186,10 +156,10 @@
   while ((w = tracker->watchers) != nullptr) {
     *w->current = state;
     tracker->watchers = w->next;
-    if (grpc_connectivity_state_trace.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_connectivity_state_trace)) {
       gpr_log(GPR_INFO, "NOTIFY: %p %s: %p", tracker, tracker->name, w->notify);
     }
-    GRPC_CLOSURE_SCHED(w->notify, GRPC_ERROR_REF(tracker->current_error));
+    GRPC_CLOSURE_SCHED(w->notify, GRPC_ERROR_NONE);
     gpr_free(w);
   }
 }
diff --git a/src/core/lib/transport/connectivity_state.h b/src/core/lib/transport/connectivity_state.h
index ecb083c..0ff1432 100644
--- a/src/core/lib/transport/connectivity_state.h
+++ b/src/core/lib/transport/connectivity_state.h
@@ -37,8 +37,6 @@
 typedef struct {
   /** current grpc_connectivity_state */
   gpr_atm current_state_atm;
-  /** error associated with state */
-  grpc_error* current_error;
   /** all our watchers */
   grpc_connectivity_state_watcher* watchers;
   /** a name to help debugging */
@@ -59,7 +57,6 @@
  *  external lock */
 void grpc_connectivity_state_set(grpc_connectivity_state_tracker* tracker,
                                  grpc_connectivity_state state,
-                                 grpc_error* associated_error,
                                  const char* reason);
 
 /** Return true if this connectivity state has watchers.
@@ -71,11 +68,6 @@
 grpc_connectivity_state grpc_connectivity_state_check(
     grpc_connectivity_state_tracker* tracker);
 
-/** Return the last seen connectivity state, and the associated error.
-    Access must be serialized with an external lock. */
-grpc_connectivity_state grpc_connectivity_state_get(
-    grpc_connectivity_state_tracker* tracker, grpc_error** error);
-
 /** Return 1 if the channel should start connecting, 0 otherwise.
     If current==NULL cancel notify if it is already queued (success==0 in that
     case).
diff --git a/src/core/lib/transport/error_utils.cc b/src/core/lib/transport/error_utils.cc
index 558f1d4..eb4e8c3 100644
--- a/src/core/lib/transport/error_utils.cc
+++ b/src/core/lib/transport/error_utils.cc
@@ -48,6 +48,18 @@
                            grpc_status_code* code, grpc_slice* slice,
                            grpc_http2_error_code* http_error,
                            const char** error_string) {
+  // Fast path: We expect no error.
+  if (GPR_LIKELY(error == GRPC_ERROR_NONE)) {
+    if (code != nullptr) *code = GRPC_STATUS_OK;
+    if (slice != nullptr) {
+      grpc_error_get_str(error, GRPC_ERROR_STR_GRPC_MESSAGE, slice);
+    }
+    if (http_error != nullptr) {
+      *http_error = GRPC_HTTP2_NO_ERROR;
+    }
+    return;
+  }
+
   // Start with the parent error and recurse through the tree of children
   // until we find the first one that has a status code.
   grpc_error* found_error =
diff --git a/src/core/lib/transport/metadata.cc b/src/core/lib/transport/metadata.cc
index b7e7fd4..4609eb4 100644
--- a/src/core/lib/transport/metadata.cc
+++ b/src/core/lib/transport/metadata.cc
@@ -41,6 +41,10 @@
 #include "src/core/lib/slice/slice_string_helpers.h"
 #include "src/core/lib/transport/static_metadata.h"
 
+using grpc_core::AllocatedMetadata;
+using grpc_core::InternedMetadata;
+using grpc_core::UserData;
+
 /* There are two kinds of mdelem and mdstr instances.
  * Static instances are declared in static_metadata.{h,c} and
  * are initialized by grpc_mdctx_global_init().
@@ -54,13 +58,40 @@
 
 #ifndef NDEBUG
 #define DEBUG_ARGS , const char *file, int line
-#define FWD_DEBUG_ARGS , file, line
-#define REF_MD_LOCKED(shard, s) ref_md_locked((shard), (s), __FILE__, __LINE__)
-#else
+#define FWD_DEBUG_ARGS file, line
+
+void grpc_mdelem_trace_ref(void* md, const grpc_slice& key,
+                           const grpc_slice& value, intptr_t refcnt,
+                           const char* file, int line) {
+  if (grpc_trace_metadata.enabled()) {
+    char* key_str = grpc_slice_to_c_string(key);
+    char* value_str = grpc_slice_to_c_string(value);
+    gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
+            "ELM   REF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'", md, refcnt,
+            refcnt + 1, key_str, value_str);
+    gpr_free(key_str);
+    gpr_free(value_str);
+  }
+}
+
+void grpc_mdelem_trace_unref(void* md, const grpc_slice& key,
+                             const grpc_slice& value, intptr_t refcnt,
+                             const char* file, int line) {
+  if (grpc_trace_metadata.enabled()) {
+    char* key_str = grpc_slice_to_c_string(key);
+    char* value_str = grpc_slice_to_c_string(value);
+    gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
+            "ELM   UNREF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'", md,
+            refcnt, refcnt - 1, key_str, value_str);
+    gpr_free(key_str);
+    gpr_free(value_str);
+  }
+}
+
+#else  // ifndef NDEBUG
 #define DEBUG_ARGS
 #define FWD_DEBUG_ARGS
-#define REF_MD_LOCKED(shard, s) ref_md_locked((shard), (s))
-#endif
+#endif  // ifndef NDEBUG
 
 #define INITIAL_SHARD_CAPACITY 8
 #define LOG2_SHARD_COUNT 4
@@ -69,43 +100,87 @@
 #define TABLE_IDX(hash, capacity) (((hash) >> (LOG2_SHARD_COUNT)) % (capacity))
 #define SHARD_IDX(hash) ((hash) & ((1 << (LOG2_SHARD_COUNT)) - 1))
 
-typedef void (*destroy_user_data_func)(void* user_data);
+AllocatedMetadata::AllocatedMetadata(const grpc_slice& key,
+                                     const grpc_slice& value)
+    : key_(grpc_slice_ref_internal(key)),
+      value_(grpc_slice_ref_internal(value)),
+      refcnt_(1) {
+#ifndef NDEBUG
+  if (grpc_trace_metadata.enabled()) {
+    char* key_str = grpc_slice_to_c_string(key_);
+    char* value_str = grpc_slice_to_c_string(value_);
+    gpr_log(GPR_DEBUG, "ELM ALLOC:%p:%" PRIdPTR ": '%s' = '%s'", this,
+            RefValue(), key_str, value_str);
+    gpr_free(key_str);
+    gpr_free(value_str);
+  }
+#endif
+}
 
-struct UserData {
-  gpr_mu mu_user_data;
-  gpr_atm destroy_user_data;
-  gpr_atm user_data;
-};
+AllocatedMetadata::~AllocatedMetadata() {
+  grpc_slice_unref_internal(key_);
+  grpc_slice_unref_internal(value_);
+  void* user_data = user_data_.data.Load(grpc_core::MemoryOrder::RELAXED);
+  if (user_data) {
+    destroy_user_data_func destroy_user_data =
+        user_data_.destroy_user_data.Load(grpc_core::MemoryOrder::RELAXED);
+    destroy_user_data(user_data);
+  }
+}
 
-/* Shadow structure for grpc_mdelem_data for interned elements */
-typedef struct interned_metadata {
-  /* must be byte compatible with grpc_mdelem_data */
-  grpc_slice key;
-  grpc_slice value;
+InternedMetadata::InternedMetadata(const grpc_slice& key,
+                                   const grpc_slice& value, uint32_t hash,
+                                   InternedMetadata* next)
+    : key_(grpc_slice_ref_internal(key)),
+      value_(grpc_slice_ref_internal(value)),
+      refcnt_(1),
+      hash_(hash),
+      link_(next) {
+#ifndef NDEBUG
+  if (grpc_trace_metadata.enabled()) {
+    char* key_str = grpc_slice_to_c_string(key_);
+    char* value_str = grpc_slice_to_c_string(value_);
+    gpr_log(GPR_DEBUG, "ELM   NEW:%p:%" PRIdPTR ": '%s' = '%s'", this,
+            RefValue(), key_str, value_str);
+    gpr_free(key_str);
+    gpr_free(value_str);
+  }
+#endif
+}
 
-  /* private only data */
-  gpr_atm refcnt;
+InternedMetadata::~InternedMetadata() {
+  grpc_slice_unref_internal(key_);
+  grpc_slice_unref_internal(value_);
+  void* user_data = user_data_.data.Load(grpc_core::MemoryOrder::RELAXED);
+  if (user_data) {
+    destroy_user_data_func destroy_user_data =
+        user_data_.destroy_user_data.Load(grpc_core::MemoryOrder::RELAXED);
+    destroy_user_data(user_data);
+  }
+}
 
-  UserData user_data;
+size_t InternedMetadata::CleanupLinkedMetadata(
+    InternedMetadata::BucketLink* head) {
+  size_t num_freed = 0;
+  InternedMetadata::BucketLink* prev_next = head;
+  InternedMetadata *md, *next;
 
-  struct interned_metadata* bucket_next;
-} interned_metadata;
-
-/* Shadow structure for grpc_mdelem_data for allocated elements */
-typedef struct allocated_metadata {
-  /* must be byte compatible with grpc_mdelem_data */
-  grpc_slice key;
-  grpc_slice value;
-
-  /* private only data */
-  gpr_atm refcnt;
-
-  UserData user_data;
-} allocated_metadata;
+  for (md = head->next; md; md = next) {
+    next = md->link_.next;
+    if (md->AllRefsDropped()) {
+      prev_next->next = next;
+      grpc_core::Delete(md);
+      num_freed++;
+    } else {
+      prev_next = &md->link_;
+    }
+  }
+  return num_freed;
+}
 
 typedef struct mdtab_shard {
   gpr_mu mu;
-  interned_metadata** elems;
+  InternedMetadata::BucketLink* elems;
   size_t count;
   size_t capacity;
   /** Estimate of the number of unreferenced mdelems in the hash table.
@@ -126,7 +201,7 @@
     shard->count = 0;
     gpr_atm_no_barrier_store(&shard->free_estimate, 0);
     shard->capacity = INITIAL_SHARD_CAPACITY;
-    shard->elems = static_cast<interned_metadata**>(
+    shard->elems = static_cast<InternedMetadata::BucketLink*>(
         gpr_zalloc(sizeof(*shard->elems) * shard->capacity));
   }
 }
@@ -136,7 +211,6 @@
     mdtab_shard* shard = &g_shards[i];
     gpr_mu_destroy(&shard->mu);
     gc_mdtab(shard);
-    /* TODO(ctiller): GPR_ASSERT(shard->count == 0); */
     if (shard->count != 0) {
       gpr_log(GPR_DEBUG, "WARNING: %" PRIuPTR " metadata elements were leaked",
               shard->count);
@@ -144,6 +218,7 @@
         abort();
       }
     }
+    GPR_DEBUG_ASSERT(shard->count == 0);
     gpr_free(shard->elems);
   }
 }
@@ -154,57 +229,34 @@
              &grpc_static_mdelem_table[GRPC_STATIC_MDELEM_COUNT];
 }
 
-static void ref_md_locked(mdtab_shard* shard,
-                          interned_metadata* md DEBUG_ARGS) {
+void InternedMetadata::RefWithShardLocked(mdtab_shard* shard) {
 #ifndef NDEBUG
   if (grpc_trace_metadata.enabled()) {
-    char* key_str = grpc_slice_to_c_string(md->key);
-    char* value_str = grpc_slice_to_c_string(md->value);
-    gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-            "ELM   REF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'", (void*)md,
-            gpr_atm_no_barrier_load(&md->refcnt),
-            gpr_atm_no_barrier_load(&md->refcnt) + 1, key_str, value_str);
+    char* key_str = grpc_slice_to_c_string(key_);
+    char* value_str = grpc_slice_to_c_string(value_);
+    intptr_t value = RefValue();
+    gpr_log(__FILE__, __LINE__, GPR_LOG_SEVERITY_DEBUG,
+            "ELM   REF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'", this, value,
+            value + 1, key_str, value_str);
     gpr_free(key_str);
     gpr_free(value_str);
   }
 #endif
-  if (0 == gpr_atm_no_barrier_fetch_add(&md->refcnt, 1)) {
+  if (FirstRef()) {
     gpr_atm_no_barrier_fetch_add(&shard->free_estimate, -1);
   }
 }
 
 static void gc_mdtab(mdtab_shard* shard) {
   GPR_TIMER_SCOPE("gc_mdtab", 0);
-
-  size_t i;
-  interned_metadata** prev_next;
-  interned_metadata *md, *next;
-  gpr_atm num_freed = 0;
-
-  for (i = 0; i < shard->capacity; i++) {
-    prev_next = &shard->elems[i];
-    for (md = shard->elems[i]; md; md = next) {
-      void* user_data =
-          (void*)gpr_atm_no_barrier_load(&md->user_data.user_data);
-      next = md->bucket_next;
-      if (gpr_atm_acq_load(&md->refcnt) == 0) {
-        grpc_slice_unref_internal(md->key);
-        grpc_slice_unref_internal(md->value);
-        if (md->user_data.user_data) {
-          ((destroy_user_data_func)gpr_atm_no_barrier_load(
-              &md->user_data.destroy_user_data))(user_data);
-        }
-        gpr_mu_destroy(&md->user_data.mu_user_data);
-        gpr_free(md);
-        *prev_next = next;
-        num_freed++;
-        shard->count--;
-      } else {
-        prev_next = &md->bucket_next;
-      }
-    }
+  size_t num_freed = 0;
+  for (size_t i = 0; i < shard->capacity; ++i) {
+    intptr_t freed = InternedMetadata::CleanupLinkedMetadata(&shard->elems[i]);
+    num_freed += freed;
+    shard->count -= freed;
   }
-  gpr_atm_no_barrier_fetch_add(&shard->free_estimate, -num_freed);
+  gpr_atm_no_barrier_fetch_add(&shard->free_estimate,
+                               -static_cast<intptr_t>(num_freed));
 }
 
 static void grow_mdtab(mdtab_shard* shard) {
@@ -212,22 +264,21 @@
 
   size_t capacity = shard->capacity * 2;
   size_t i;
-  interned_metadata** mdtab;
-  interned_metadata *md, *next;
+  InternedMetadata::BucketLink* mdtab;
+  InternedMetadata *md, *next;
   uint32_t hash;
 
-  mdtab = static_cast<interned_metadata**>(
-      gpr_zalloc(sizeof(interned_metadata*) * capacity));
+  mdtab = static_cast<InternedMetadata::BucketLink*>(
+      gpr_zalloc(sizeof(InternedMetadata::BucketLink) * capacity));
 
   for (i = 0; i < shard->capacity; i++) {
-    for (md = shard->elems[i]; md; md = next) {
+    for (md = shard->elems[i].next; md; md = next) {
       size_t idx;
-      hash = GRPC_MDSTR_KV_HASH(grpc_slice_hash(md->key),
-                                grpc_slice_hash(md->value));
-      next = md->bucket_next;
+      hash = md->hash();
+      next = md->bucket_next();
       idx = TABLE_IDX(hash, capacity);
-      md->bucket_next = mdtab[idx];
-      mdtab[idx] = md;
+      md->set_bucket_next(mdtab[idx].next);
+      mdtab[idx].next = md;
     }
   }
   gpr_free(shard->elems);
@@ -247,34 +298,22 @@
 grpc_mdelem grpc_mdelem_create(
     const grpc_slice& key, const grpc_slice& value,
     grpc_mdelem_data* compatible_external_backing_store) {
+  // External storage if either slice is not interned and the caller already
+  // created a backing store. If no backing store, we allocate one.
   if (!grpc_slice_is_interned(key) || !grpc_slice_is_interned(value)) {
     if (compatible_external_backing_store != nullptr) {
+      // Caller provided backing store.
       return GRPC_MAKE_MDELEM(compatible_external_backing_store,
                               GRPC_MDELEM_STORAGE_EXTERNAL);
+    } else {
+      // We allocate backing store.
+      return GRPC_MAKE_MDELEM(grpc_core::New<AllocatedMetadata>(key, value),
+                              GRPC_MDELEM_STORAGE_ALLOCATED);
     }
-
-    allocated_metadata* allocated =
-        static_cast<allocated_metadata*>(gpr_malloc(sizeof(*allocated)));
-    allocated->key = grpc_slice_ref_internal(key);
-    allocated->value = grpc_slice_ref_internal(value);
-    gpr_atm_rel_store(&allocated->refcnt, 1);
-    allocated->user_data.user_data = 0;
-    allocated->user_data.destroy_user_data = 0;
-    gpr_mu_init(&allocated->user_data.mu_user_data);
-#ifndef NDEBUG
-    if (grpc_trace_metadata.enabled()) {
-      char* key_str = grpc_slice_to_c_string(allocated->key);
-      char* value_str = grpc_slice_to_c_string(allocated->value);
-      gpr_log(GPR_DEBUG, "ELM ALLOC:%p:%" PRIdPTR ": '%s' = '%s'",
-              (void*)allocated, gpr_atm_no_barrier_load(&allocated->refcnt),
-              key_str, value_str);
-      gpr_free(key_str);
-      gpr_free(value_str);
-    }
-#endif
-    return GRPC_MAKE_MDELEM(allocated, GRPC_MDELEM_STORAGE_ALLOCATED);
   }
 
+  // Not all static slice input yields a statically stored metadata element.
+  // It may be worth documenting why.
   if (GRPC_IS_STATIC_METADATA_STRING(key) &&
       GRPC_IS_STATIC_METADATA_STRING(value)) {
     grpc_mdelem static_elem = grpc_static_mdelem_for_static_strings(
@@ -286,7 +325,7 @@
 
   uint32_t hash =
       GRPC_MDSTR_KV_HASH(grpc_slice_hash(key), grpc_slice_hash(value));
-  interned_metadata* md;
+  InternedMetadata* md;
   mdtab_shard* shard = &g_shards[SHARD_IDX(hash)];
   size_t idx;
 
@@ -296,34 +335,18 @@
 
   idx = TABLE_IDX(hash, shard->capacity);
   /* search for an existing pair */
-  for (md = shard->elems[idx]; md; md = md->bucket_next) {
-    if (grpc_slice_eq(key, md->key) && grpc_slice_eq(value, md->value)) {
-      REF_MD_LOCKED(shard, md);
+  for (md = shard->elems[idx].next; md; md = md->bucket_next()) {
+    if (grpc_slice_eq(key, md->key()) && grpc_slice_eq(value, md->value())) {
+      md->RefWithShardLocked(shard);
       gpr_mu_unlock(&shard->mu);
       return GRPC_MAKE_MDELEM(md, GRPC_MDELEM_STORAGE_INTERNED);
     }
   }
 
   /* not found: create a new pair */
-  md = static_cast<interned_metadata*>(gpr_malloc(sizeof(interned_metadata)));
-  gpr_atm_rel_store(&md->refcnt, 1);
-  md->key = grpc_slice_ref_internal(key);
-  md->value = grpc_slice_ref_internal(value);
-  md->user_data.user_data = 0;
-  md->user_data.destroy_user_data = 0;
-  md->bucket_next = shard->elems[idx];
-  shard->elems[idx] = md;
-  gpr_mu_init(&md->user_data.mu_user_data);
-#ifndef NDEBUG
-  if (grpc_trace_metadata.enabled()) {
-    char* key_str = grpc_slice_to_c_string(md->key);
-    char* value_str = grpc_slice_to_c_string(md->value);
-    gpr_log(GPR_DEBUG, "ELM   NEW:%p:%" PRIdPTR ": '%s' = '%s'", (void*)md,
-            gpr_atm_no_barrier_load(&md->refcnt), key_str, value_str);
-    gpr_free(key_str);
-    gpr_free(value_str);
-  }
-#endif
+  md = grpc_core::New<InternedMetadata>(key, value, hash,
+                                        shard->elems[idx].next);
+  shard->elems[idx].next = md;
   shard->count++;
 
   if (shard->count > shard->capacity * 2) {
@@ -354,130 +377,10 @@
       changed ? nullptr : reinterpret_cast<grpc_mdelem_data*>(metadata));
 }
 
-grpc_mdelem grpc_mdelem_ref(grpc_mdelem gmd DEBUG_ARGS) {
-  switch (GRPC_MDELEM_STORAGE(gmd)) {
-    case GRPC_MDELEM_STORAGE_EXTERNAL:
-    case GRPC_MDELEM_STORAGE_STATIC:
-      break;
-    case GRPC_MDELEM_STORAGE_INTERNED: {
-      interned_metadata* md =
-          reinterpret_cast<interned_metadata*> GRPC_MDELEM_DATA(gmd);
-#ifndef NDEBUG
-      if (grpc_trace_metadata.enabled()) {
-        char* key_str = grpc_slice_to_c_string(md->key);
-        char* value_str = grpc_slice_to_c_string(md->value);
-        gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-                "ELM   REF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'",
-                (void*)md, gpr_atm_no_barrier_load(&md->refcnt),
-                gpr_atm_no_barrier_load(&md->refcnt) + 1, key_str, value_str);
-        gpr_free(key_str);
-        gpr_free(value_str);
-      }
-#endif
-      /* we can assume the ref count is >= 1 as the application is calling
-         this function - meaning that no adjustment to mdtab_free is necessary,
-         simplifying the logic here to be just an atomic increment */
-      /* use C assert to have this removed in opt builds */
-      GPR_ASSERT(gpr_atm_no_barrier_load(&md->refcnt) >= 1);
-      gpr_atm_no_barrier_fetch_add(&md->refcnt, 1);
-      break;
-    }
-    case GRPC_MDELEM_STORAGE_ALLOCATED: {
-      allocated_metadata* md =
-          reinterpret_cast<allocated_metadata*> GRPC_MDELEM_DATA(gmd);
-#ifndef NDEBUG
-      if (grpc_trace_metadata.enabled()) {
-        char* key_str = grpc_slice_to_c_string(md->key);
-        char* value_str = grpc_slice_to_c_string(md->value);
-        gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-                "ELM   REF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'",
-                (void*)md, gpr_atm_no_barrier_load(&md->refcnt),
-                gpr_atm_no_barrier_load(&md->refcnt) + 1, key_str, value_str);
-        gpr_free(key_str);
-        gpr_free(value_str);
-      }
-#endif
-      /* we can assume the ref count is >= 1 as the application is calling
-         this function - meaning that no adjustment to mdtab_free is necessary,
-         simplifying the logic here to be just an atomic increment */
-      /* use C assert to have this removed in opt builds */
-      gpr_atm_no_barrier_fetch_add(&md->refcnt, 1);
-      break;
-    }
-  }
-  return gmd;
-}
-
-void grpc_mdelem_unref(grpc_mdelem gmd DEBUG_ARGS) {
-  switch (GRPC_MDELEM_STORAGE(gmd)) {
-    case GRPC_MDELEM_STORAGE_EXTERNAL:
-    case GRPC_MDELEM_STORAGE_STATIC:
-      break;
-    case GRPC_MDELEM_STORAGE_INTERNED: {
-      interned_metadata* md =
-          reinterpret_cast<interned_metadata*> GRPC_MDELEM_DATA(gmd);
-#ifndef NDEBUG
-      if (grpc_trace_metadata.enabled()) {
-        char* key_str = grpc_slice_to_c_string(md->key);
-        char* value_str = grpc_slice_to_c_string(md->value);
-        gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-                "ELM UNREF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'",
-                (void*)md, gpr_atm_no_barrier_load(&md->refcnt),
-                gpr_atm_no_barrier_load(&md->refcnt) - 1, key_str, value_str);
-        gpr_free(key_str);
-        gpr_free(value_str);
-      }
-#endif
-      uint32_t hash = GRPC_MDSTR_KV_HASH(grpc_slice_hash(md->key),
-                                         grpc_slice_hash(md->value));
-      const gpr_atm prev_refcount = gpr_atm_full_fetch_add(&md->refcnt, -1);
-      GPR_ASSERT(prev_refcount >= 1);
-      if (1 == prev_refcount) {
-        /* once the refcount hits zero, some other thread can come along and
-           free md at any time: it's unsafe from this point on to access it */
-        mdtab_shard* shard = &g_shards[SHARD_IDX(hash)];
-        gpr_atm_no_barrier_fetch_add(&shard->free_estimate, 1);
-      }
-      break;
-    }
-    case GRPC_MDELEM_STORAGE_ALLOCATED: {
-      allocated_metadata* md =
-          reinterpret_cast<allocated_metadata*> GRPC_MDELEM_DATA(gmd);
-#ifndef NDEBUG
-      if (grpc_trace_metadata.enabled()) {
-        char* key_str = grpc_slice_to_c_string(md->key);
-        char* value_str = grpc_slice_to_c_string(md->value);
-        gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-                "ELM UNREF:%p:%" PRIdPTR "->%" PRIdPTR ": '%s' = '%s'",
-                (void*)md, gpr_atm_no_barrier_load(&md->refcnt),
-                gpr_atm_no_barrier_load(&md->refcnt) - 1, key_str, value_str);
-        gpr_free(key_str);
-        gpr_free(value_str);
-      }
-#endif
-      const gpr_atm prev_refcount = gpr_atm_full_fetch_add(&md->refcnt, -1);
-      GPR_ASSERT(prev_refcount >= 1);
-      if (1 == prev_refcount) {
-        grpc_slice_unref_internal(md->key);
-        grpc_slice_unref_internal(md->value);
-        if (md->user_data.user_data) {
-          destroy_user_data_func destroy_user_data =
-              (destroy_user_data_func)gpr_atm_no_barrier_load(
-                  &md->user_data.destroy_user_data);
-          destroy_user_data((void*)md->user_data.user_data);
-        }
-        gpr_mu_destroy(&md->user_data.mu_user_data);
-        gpr_free(md);
-      }
-      break;
-    }
-  }
-}
-
 static void* get_user_data(UserData* user_data, void (*destroy_func)(void*)) {
-  if (gpr_atm_acq_load(&user_data->destroy_user_data) ==
-      (gpr_atm)destroy_func) {
-    return (void*)gpr_atm_no_barrier_load(&user_data->user_data);
+  if (user_data->destroy_user_data.Load(grpc_core::MemoryOrder::ACQUIRE) ==
+      destroy_func) {
+    return user_data->data.Load(grpc_core::MemoryOrder::RELAXED);
   } else {
     return nullptr;
   }
@@ -491,57 +394,52 @@
       return (void*)grpc_static_mdelem_user_data[GRPC_MDELEM_DATA(md) -
                                                  grpc_static_mdelem_table];
     case GRPC_MDELEM_STORAGE_ALLOCATED: {
-      allocated_metadata* am =
-          reinterpret_cast<allocated_metadata*>(GRPC_MDELEM_DATA(md));
-      return get_user_data(&am->user_data, destroy_func);
+      auto* am = reinterpret_cast<AllocatedMetadata*>(GRPC_MDELEM_DATA(md));
+      return get_user_data(am->user_data(), destroy_func);
     }
     case GRPC_MDELEM_STORAGE_INTERNED: {
-      interned_metadata* im =
-          reinterpret_cast<interned_metadata*> GRPC_MDELEM_DATA(md);
-      return get_user_data(&im->user_data, destroy_func);
+      auto* im = reinterpret_cast<InternedMetadata*> GRPC_MDELEM_DATA(md);
+      return get_user_data(im->user_data(), destroy_func);
     }
   }
   GPR_UNREACHABLE_CODE(return nullptr);
 }
 
 static void* set_user_data(UserData* ud, void (*destroy_func)(void*),
-                           void* user_data) {
-  GPR_ASSERT((user_data == nullptr) == (destroy_func == nullptr));
-  gpr_mu_lock(&ud->mu_user_data);
-  if (gpr_atm_no_barrier_load(&ud->destroy_user_data)) {
+                           void* data) {
+  GPR_ASSERT((data == nullptr) == (destroy_func == nullptr));
+  grpc_core::ReleasableMutexLock lock(&ud->mu_user_data);
+  if (ud->destroy_user_data.Load(grpc_core::MemoryOrder::RELAXED)) {
     /* user data can only be set once */
-    gpr_mu_unlock(&ud->mu_user_data);
+    lock.Unlock();
     if (destroy_func != nullptr) {
-      destroy_func(user_data);
+      destroy_func(data);
     }
-    return (void*)gpr_atm_no_barrier_load(&ud->user_data);
+    return ud->data.Load(grpc_core::MemoryOrder::RELAXED);
   }
-  gpr_atm_no_barrier_store(&ud->user_data, (gpr_atm)user_data);
-  gpr_atm_rel_store(&ud->destroy_user_data, (gpr_atm)destroy_func);
-  gpr_mu_unlock(&ud->mu_user_data);
-  return user_data;
+  ud->data.Store(data, grpc_core::MemoryOrder::RELAXED);
+  ud->destroy_user_data.Store(destroy_func, grpc_core::MemoryOrder::RELEASE);
+  return data;
 }
 
 void* grpc_mdelem_set_user_data(grpc_mdelem md, void (*destroy_func)(void*),
-                                void* user_data) {
+                                void* data) {
   switch (GRPC_MDELEM_STORAGE(md)) {
     case GRPC_MDELEM_STORAGE_EXTERNAL:
-      destroy_func(user_data);
+      destroy_func(data);
       return nullptr;
     case GRPC_MDELEM_STORAGE_STATIC:
-      destroy_func(user_data);
+      destroy_func(data);
       return (void*)grpc_static_mdelem_user_data[GRPC_MDELEM_DATA(md) -
                                                  grpc_static_mdelem_table];
     case GRPC_MDELEM_STORAGE_ALLOCATED: {
-      allocated_metadata* am =
-          reinterpret_cast<allocated_metadata*>(GRPC_MDELEM_DATA(md));
-      return set_user_data(&am->user_data, destroy_func, user_data);
+      auto* am = reinterpret_cast<AllocatedMetadata*>(GRPC_MDELEM_DATA(md));
+      return set_user_data(am->user_data(), destroy_func, data);
     }
     case GRPC_MDELEM_STORAGE_INTERNED: {
-      interned_metadata* im =
-          reinterpret_cast<interned_metadata*> GRPC_MDELEM_DATA(md);
+      auto* im = reinterpret_cast<InternedMetadata*> GRPC_MDELEM_DATA(md);
       GPR_ASSERT(!is_mdelem_static(md));
-      return set_user_data(&im->user_data, destroy_func, user_data);
+      return set_user_data(im->user_data(), destroy_func, data);
     }
   }
   GPR_UNREACHABLE_CODE(return nullptr);
@@ -554,3 +452,33 @@
   return grpc_slice_eq(GRPC_MDKEY(a), GRPC_MDKEY(b)) &&
          grpc_slice_eq(GRPC_MDVALUE(a), GRPC_MDVALUE(b));
 }
+
+static void note_disposed_interned_metadata(uint32_t hash) {
+  mdtab_shard* shard = &g_shards[SHARD_IDX(hash)];
+  gpr_atm_no_barrier_fetch_add(&shard->free_estimate, 1);
+}
+
+void grpc_mdelem_do_unref(grpc_mdelem gmd DEBUG_ARGS) {
+  switch (GRPC_MDELEM_STORAGE(gmd)) {
+    case GRPC_MDELEM_STORAGE_EXTERNAL:
+    case GRPC_MDELEM_STORAGE_STATIC:
+      return;
+    case GRPC_MDELEM_STORAGE_INTERNED: {
+      auto* md = reinterpret_cast<InternedMetadata*> GRPC_MDELEM_DATA(gmd);
+      uint32_t hash = md->hash();
+      if (GPR_UNLIKELY(md->Unref(FWD_DEBUG_ARGS))) {
+        /* once the refcount hits zero, some other thread can come along and
+           free md at any time: it's unsafe from this point on to access it */
+        note_disposed_interned_metadata(hash);
+      }
+      break;
+    }
+    case GRPC_MDELEM_STORAGE_ALLOCATED: {
+      auto* md = reinterpret_cast<AllocatedMetadata*> GRPC_MDELEM_DATA(gmd);
+      if (GPR_UNLIKELY(md->Unref(FWD_DEBUG_ARGS))) {
+        grpc_core::Delete(md);
+      }
+      break;
+    }
+  }
+}
diff --git a/src/core/lib/transport/metadata.h b/src/core/lib/transport/metadata.h
index 989c754..c0d1ab3 100644
--- a/src/core/lib/transport/metadata.h
+++ b/src/core/lib/transport/metadata.h
@@ -21,11 +21,15 @@
 
 #include <grpc/support/port_platform.h>
 
+#include "include/grpc/impl/codegen/log.h"
+
 #include <grpc/grpc.h>
 #include <grpc/slice.h>
 
 #include "src/core/lib/debug/trace.h"
 #include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/atomic.h"
+#include "src/core/lib/gprpp/sync.h"
 
 extern grpc_core::DebugOnlyTraceFlag grpc_trace_metadata;
 
@@ -63,7 +67,7 @@
 typedef struct grpc_mdelem grpc_mdelem;
 
 /* if changing this, make identical changes in:
-   - interned_metadata, allocated_metadata in metadata.c
+   - grpc_core::{InternedMetadata, AllocatedMetadata}
    - grpc_metadata in grpc_types.h */
 typedef struct grpc_mdelem_data {
   const grpc_slice key;
@@ -124,28 +128,219 @@
     const grpc_slice& key, const grpc_slice& value,
     grpc_mdelem_data* compatible_external_backing_store);
 
+#define GRPC_MDKEY(md) (GRPC_MDELEM_DATA(md)->key)
+#define GRPC_MDVALUE(md) (GRPC_MDELEM_DATA(md)->value)
+
 bool grpc_mdelem_eq(grpc_mdelem a, grpc_mdelem b);
+/* Often we compare metadata where we know a-priori that the second parameter is
+ * static, and that the keys match. This most commonly happens when processing
+ * metadata batch callouts in initial/trailing filters. In this case, fastpath
+ * grpc_mdelem_eq and remove unnecessary checks. */
+inline bool grpc_mdelem_static_value_eq(grpc_mdelem a, grpc_mdelem b_static) {
+  if (a.payload == b_static.payload) return true;
+  return grpc_slice_eq(GRPC_MDVALUE(a), GRPC_MDVALUE(b_static));
+}
 
 /* Mutator and accessor for grpc_mdelem user data. The destructor function
    is used as a type tag and is checked during user_data fetch. */
 void* grpc_mdelem_get_user_data(grpc_mdelem md, void (*if_destroy_func)(void*));
 void* grpc_mdelem_set_user_data(grpc_mdelem md, void (*destroy_func)(void*),
-                                void* user_data);
+                                void* data);
+
+// Defined in metadata.cc.
+struct mdtab_shard;
+
+#ifndef NDEBUG
+void grpc_mdelem_trace_ref(void* md, const grpc_slice& key,
+                           const grpc_slice& value, intptr_t refcnt,
+                           const char* file, int line);
+void grpc_mdelem_trace_unref(void* md, const grpc_slice& key,
+                             const grpc_slice& value, intptr_t refcnt,
+                             const char* file, int line);
+#endif
+namespace grpc_core {
+
+typedef void (*destroy_user_data_func)(void* data);
+
+struct UserData {
+  Mutex mu_user_data;
+  grpc_core::Atomic<destroy_user_data_func> destroy_user_data;
+  grpc_core::Atomic<void*> data;
+};
+
+class InternedMetadata {
+ public:
+  struct BucketLink {
+    explicit BucketLink(InternedMetadata* md) : next(md) {}
+
+    InternedMetadata* next = nullptr;
+  };
+
+  InternedMetadata(const grpc_slice& key, const grpc_slice& value,
+                   uint32_t hash, InternedMetadata* next);
+  ~InternedMetadata();
+
+#ifndef NDEBUG
+  void Ref(const char* file, int line) {
+    grpc_mdelem_trace_ref(this, key_, value_, RefValue(), file, line);
+    const intptr_t prior = refcnt_.FetchAdd(1, MemoryOrder::RELAXED);
+    GPR_ASSERT(prior > 0);
+  }
+  bool Unref(const char* file, int line) {
+    grpc_mdelem_trace_unref(this, key_, value_, RefValue(), file, line);
+    return Unref();
+  }
+#else
+  // We define a naked Ref() in the else-clause to make sure we don't
+  // inadvertently skip the assert on debug builds.
+  void Ref() {
+    /* we can assume the ref count is >= 1 as the application is calling
+       this function - meaning that no adjustment to mdtab_free is necessary,
+       simplifying the logic here to be just an atomic increment */
+    refcnt_.FetchAdd(1, MemoryOrder::RELAXED);
+  }
+#endif  // ifndef NDEBUG
+  bool Unref() {
+    const intptr_t prior = refcnt_.FetchSub(1, MemoryOrder::ACQ_REL);
+    GPR_DEBUG_ASSERT(prior > 0);
+    return prior == 1;
+  }
+
+  void RefWithShardLocked(mdtab_shard* shard);
+  const grpc_slice& key() const { return key_; }
+  const grpc_slice& value() const { return value_; }
+  UserData* user_data() { return &user_data_; }
+  uint32_t hash() { return hash_; }
+  InternedMetadata* bucket_next() { return link_.next; }
+  void set_bucket_next(InternedMetadata* md) { link_.next = md; }
+
+  static size_t CleanupLinkedMetadata(BucketLink* head);
+
+ private:
+  bool AllRefsDropped() { return refcnt_.Load(MemoryOrder::ACQUIRE) == 0; }
+  bool FirstRef() { return refcnt_.FetchAdd(1, MemoryOrder::RELAXED) == 0; }
+  intptr_t RefValue() { return refcnt_.Load(MemoryOrder::RELAXED); }
+
+  /* must be byte compatible with grpc_mdelem_data */
+  grpc_slice key_;
+  grpc_slice value_;
+
+  /* private only data */
+  grpc_core::Atomic<intptr_t> refcnt_;
+  uint32_t hash_;
+
+  UserData user_data_;
+
+  BucketLink link_;
+};
+
+/* Shadow structure for grpc_mdelem_data for allocated elements */
+class AllocatedMetadata {
+ public:
+  AllocatedMetadata(const grpc_slice& key, const grpc_slice& value);
+  ~AllocatedMetadata();
+
+  const grpc_slice& key() const { return key_; }
+  const grpc_slice& value() const { return value_; }
+  UserData* user_data() { return &user_data_; }
+
+#ifndef NDEBUG
+  void Ref(const char* file, int line) {
+    grpc_mdelem_trace_ref(this, key_, value_, RefValue(), file, line);
+    Ref();
+  }
+  bool Unref(const char* file, int line) {
+    grpc_mdelem_trace_unref(this, key_, value_, RefValue(), file, line);
+    return Unref();
+  }
+#endif  // ifndef NDEBUG
+  void Ref() {
+    /* we can assume the ref count is >= 1 as the application is calling
+       this function - meaning that no adjustment to mdtab_free is necessary,
+       simplifying the logic here to be just an atomic increment */
+    refcnt_.FetchAdd(1, MemoryOrder::RELAXED);
+  }
+  bool Unref() {
+    const intptr_t prior = refcnt_.FetchSub(1, MemoryOrder::ACQ_REL);
+    GPR_DEBUG_ASSERT(prior > 0);
+    return prior == 1;
+  }
+
+ private:
+  intptr_t RefValue() { return refcnt_.Load(MemoryOrder::RELAXED); }
+
+  /* must be byte compatible with grpc_mdelem_data */
+  grpc_slice key_;
+  grpc_slice value_;
+
+  /* private only data */
+  grpc_core::Atomic<intptr_t> refcnt_;
+
+  UserData user_data_;
+};
+
+}  // namespace grpc_core
 
 #ifndef NDEBUG
 #define GRPC_MDELEM_REF(s) grpc_mdelem_ref((s), __FILE__, __LINE__)
-#define GRPC_MDELEM_UNREF(s) grpc_mdelem_unref((s), __FILE__, __LINE__)
-grpc_mdelem grpc_mdelem_ref(grpc_mdelem md, const char* file, int line);
-void grpc_mdelem_unref(grpc_mdelem md, const char* file, int line);
-#else
+inline grpc_mdelem grpc_mdelem_ref(grpc_mdelem gmd, const char* file,
+                                   int line) {
+#else  // ifndef NDEBUG
 #define GRPC_MDELEM_REF(s) grpc_mdelem_ref((s))
-#define GRPC_MDELEM_UNREF(s) grpc_mdelem_unref((s))
-grpc_mdelem grpc_mdelem_ref(grpc_mdelem md);
-void grpc_mdelem_unref(grpc_mdelem md);
+inline grpc_mdelem grpc_mdelem_ref(grpc_mdelem gmd) {
+#endif  // ifndef NDEBUG
+  switch (GRPC_MDELEM_STORAGE(gmd)) {
+    case GRPC_MDELEM_STORAGE_EXTERNAL:
+    case GRPC_MDELEM_STORAGE_STATIC:
+      break;
+    case GRPC_MDELEM_STORAGE_INTERNED: {
+      auto* md =
+          reinterpret_cast<grpc_core::InternedMetadata*> GRPC_MDELEM_DATA(gmd);
+      /* use C assert to have this removed in opt builds */
+#ifndef NDEBUG
+      md->Ref(file, line);
+#else
+      md->Ref();
 #endif
+      break;
+    }
+    case GRPC_MDELEM_STORAGE_ALLOCATED: {
+      auto* md =
+          reinterpret_cast<grpc_core::AllocatedMetadata*> GRPC_MDELEM_DATA(gmd);
+#ifndef NDEBUG
+      md->Ref(file, line);
+#else
+      md->Ref();
+#endif
+      break;
+    }
+  }
+  return gmd;
+}
 
-#define GRPC_MDKEY(md) (GRPC_MDELEM_DATA(md)->key)
-#define GRPC_MDVALUE(md) (GRPC_MDELEM_DATA(md)->value)
+#ifndef NDEBUG
+#define GRPC_MDELEM_UNREF(s) grpc_mdelem_unref((s), __FILE__, __LINE__)
+void grpc_mdelem_do_unref(grpc_mdelem gmd, const char* file, int line);
+inline void grpc_mdelem_unref(grpc_mdelem gmd, const char* file, int line) {
+#else
+#define GRPC_MDELEM_UNREF(s) grpc_mdelem_unref((s))
+void grpc_mdelem_do_unref(grpc_mdelem gmd);
+inline void grpc_mdelem_unref(grpc_mdelem gmd) {
+#endif
+  switch (GRPC_MDELEM_STORAGE(gmd)) {
+    case GRPC_MDELEM_STORAGE_EXTERNAL:
+    case GRPC_MDELEM_STORAGE_STATIC:
+      return;
+    case GRPC_MDELEM_STORAGE_INTERNED:
+    case GRPC_MDELEM_STORAGE_ALLOCATED:
+#ifndef NDEBUG
+      grpc_mdelem_do_unref(gmd, file, line);
+#else
+      grpc_mdelem_do_unref(gmd);
+#endif
+      return;
+  }
+}
 
 #define GRPC_MDNULL GRPC_MAKE_MDELEM(NULL, GRPC_MDELEM_STORAGE_EXTERNAL)
 #define GRPC_MDISNULL(md) (GRPC_MDELEM_DATA(md) == NULL)
diff --git a/src/core/lib/transport/static_metadata.cc b/src/core/lib/transport/static_metadata.cc
index 963626a..fc5bb64 100644
--- a/src/core/lib/transport/static_metadata.cc
+++ b/src/core/lib/transport/static_metadata.cc
@@ -116,123 +116,115 @@
     103, 122, 105, 112, 105, 100, 101, 110, 116, 105, 116, 121, 44,  100, 101,
     102, 108, 97,  116, 101, 44,  103, 122, 105, 112};
 
-static void static_ref(void* unused) {}
-static void static_unref(void* unused) {}
-static const grpc_slice_refcount_vtable static_sub_vtable = {
-    static_ref, static_unref, grpc_slice_default_eq_impl,
-    grpc_slice_default_hash_impl};
-const grpc_slice_refcount_vtable grpc_static_metadata_vtable = {
-    static_ref, static_unref, grpc_static_slice_eq, grpc_static_slice_hash};
-static grpc_slice_refcount static_sub_refcnt = {&static_sub_vtable,
-                                                &static_sub_refcnt};
+static grpc_slice_refcount static_sub_refcnt;
 grpc_slice_refcount grpc_static_metadata_refcounts[GRPC_STATIC_MDSTR_COUNT] = {
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
-    {&grpc_static_metadata_vtable, &static_sub_refcnt},
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
+    grpc_slice_refcount(&static_sub_refcnt, grpc_slice_refcount::Type::STATIC),
 };
 
 const grpc_slice grpc_static_slice_table[GRPC_STATIC_MDSTR_COUNT] = {
diff --git a/src/core/lib/transport/static_metadata.h b/src/core/lib/transport/static_metadata.h
index 4f96702..88293ae 100644
--- a/src/core/lib/transport/static_metadata.h
+++ b/src/core/lib/transport/static_metadata.h
@@ -256,12 +256,11 @@
 #define GRPC_MDSTR_IDENTITY_COMMA_DEFLATE_COMMA_GZIP \
   (grpc_static_slice_table[106])
 
-extern const grpc_slice_refcount_vtable grpc_static_metadata_vtable;
 extern grpc_slice_refcount
     grpc_static_metadata_refcounts[GRPC_STATIC_MDSTR_COUNT];
 #define GRPC_IS_STATIC_METADATA_STRING(slice) \
   ((slice).refcount != NULL &&                \
-   (slice).refcount->vtable == &grpc_static_metadata_vtable)
+   (slice).refcount->GetType() == grpc_slice_refcount::Type::STATIC)
 
 #define GRPC_STATIC_METADATA_INDEX(static_slice) \
   ((int)((static_slice).refcount - grpc_static_metadata_refcounts))
diff --git a/src/core/lib/transport/status_metadata.cc b/src/core/lib/transport/status_metadata.cc
index f896053..8ef9682 100644
--- a/src/core/lib/transport/status_metadata.cc
+++ b/src/core/lib/transport/status_metadata.cc
@@ -31,13 +31,13 @@
 static void destroy_status(void* ignored) {}
 
 grpc_status_code grpc_get_status_code_from_metadata(grpc_mdelem md) {
-  if (grpc_mdelem_eq(md, GRPC_MDELEM_GRPC_STATUS_0)) {
+  if (grpc_mdelem_static_value_eq(md, GRPC_MDELEM_GRPC_STATUS_0)) {
     return GRPC_STATUS_OK;
   }
-  if (grpc_mdelem_eq(md, GRPC_MDELEM_GRPC_STATUS_1)) {
+  if (grpc_mdelem_static_value_eq(md, GRPC_MDELEM_GRPC_STATUS_1)) {
     return GRPC_STATUS_CANCELLED;
   }
-  if (grpc_mdelem_eq(md, GRPC_MDELEM_GRPC_STATUS_2)) {
+  if (grpc_mdelem_static_value_eq(md, GRPC_MDELEM_GRPC_STATUS_2)) {
     return GRPC_STATUS_UNKNOWN;
   }
   void* user_data = grpc_mdelem_get_user_data(md, destroy_status);
diff --git a/src/core/lib/transport/transport.cc b/src/core/lib/transport/transport.cc
index 0930611..29c1e56 100644
--- a/src/core/lib/transport/transport.cc
+++ b/src/core/lib/transport/transport.cc
@@ -39,72 +39,39 @@
 grpc_core::DebugOnlyTraceFlag grpc_trace_stream_refcount(false,
                                                          "stream_refcount");
 
-#ifndef NDEBUG
-void grpc_stream_ref(grpc_stream_refcount* refcount, const char* reason) {
-  if (grpc_trace_stream_refcount.enabled()) {
-    gpr_atm val = gpr_atm_no_barrier_load(&refcount->refs.count);
-    gpr_log(GPR_DEBUG, "%s %p:%p   REF %" PRIdPTR "->%" PRIdPTR " %s",
-            refcount->object_type, refcount, refcount->destroy.cb_arg, val,
-            val + 1, reason);
+void grpc_stream_destroy(grpc_stream_refcount* refcount) {
+  if (!grpc_iomgr_is_any_background_poller_thread() &&
+      (grpc_core::ExecCtx::Get()->flags() &
+       GRPC_EXEC_CTX_FLAG_THREAD_RESOURCE_LOOP)) {
+    /* Ick.
+       The thread we're running on MAY be owned (indirectly) by a call-stack.
+       If that's the case, destroying the call-stack MAY try to destroy the
+       thread, which is a tangled mess that we just don't want to ever have to
+       cope with.
+       Throw this over to the executor (on a core-owned thread) and process it
+       there. */
+    refcount->destroy.scheduler =
+        grpc_core::Executor::Scheduler(grpc_core::ExecutorJobType::SHORT);
   }
-#else
-void grpc_stream_ref(grpc_stream_refcount* refcount) {
-#endif
-  gpr_ref_non_zero(&refcount->refs);
+  GRPC_CLOSURE_SCHED(&refcount->destroy, GRPC_ERROR_NONE);
 }
 
-#ifndef NDEBUG
-void grpc_stream_unref(grpc_stream_refcount* refcount, const char* reason) {
-  if (grpc_trace_stream_refcount.enabled()) {
-    gpr_atm val = gpr_atm_no_barrier_load(&refcount->refs.count);
-    gpr_log(GPR_DEBUG, "%s %p:%p UNREF %" PRIdPTR "->%" PRIdPTR " %s",
-            refcount->object_type, refcount, refcount->destroy.cb_arg, val,
-            val - 1, reason);
-  }
-#else
-void grpc_stream_unref(grpc_stream_refcount* refcount) {
-#endif
-  if (gpr_unref(&refcount->refs)) {
-    if (!grpc_iomgr_is_any_background_poller_thread() &&
-        (grpc_core::ExecCtx::Get()->flags() &
-         GRPC_EXEC_CTX_FLAG_THREAD_RESOURCE_LOOP)) {
-      /* Ick.
-         The thread we're running on MAY be owned (indirectly) by a call-stack.
-         If that's the case, destroying the call-stack MAY try to destroy the
-         thread, which is a tangled mess that we just don't want to ever have to
-         cope with.
-         Throw this over to the executor (on a core-owned thread) and process it
-         there. */
-      refcount->destroy.scheduler =
-          grpc_core::Executor::Scheduler(grpc_core::ExecutorJobType::SHORT);
-    }
-    GRPC_CLOSURE_SCHED(&refcount->destroy, GRPC_ERROR_NONE);
-  }
+void slice_stream_destroy(void* arg) {
+  grpc_stream_destroy(static_cast<grpc_stream_refcount*>(arg));
 }
 
 #define STREAM_REF_FROM_SLICE_REF(p)       \
   ((grpc_stream_refcount*)(((uint8_t*)p) - \
                            offsetof(grpc_stream_refcount, slice_refcount)))
 
-static void slice_stream_ref(void* p) {
-#ifndef NDEBUG
-  grpc_stream_ref(STREAM_REF_FROM_SLICE_REF(p), "slice");
-#else
-  grpc_stream_ref(STREAM_REF_FROM_SLICE_REF(p));
-#endif
-}
-
-static void slice_stream_unref(void* p) {
-#ifndef NDEBUG
-  grpc_stream_unref(STREAM_REF_FROM_SLICE_REF(p), "slice");
-#else
-  grpc_stream_unref(STREAM_REF_FROM_SLICE_REF(p));
-#endif
-}
-
 grpc_slice grpc_slice_from_stream_owned_buffer(grpc_stream_refcount* refcount,
                                                void* buffer, size_t length) {
-  slice_stream_ref(&refcount->slice_refcount);
+#ifndef NDEBUG
+  grpc_stream_ref(STREAM_REF_FROM_SLICE_REF(&refcount->slice_refcount),
+                  "slice");
+#else
+  grpc_stream_ref(STREAM_REF_FROM_SLICE_REF(&refcount->slice_refcount));
+#endif
   grpc_slice res;
   res.refcount = &refcount->slice_refcount;
   res.data.refcounted.bytes = static_cast<uint8_t*>(buffer);
@@ -112,13 +79,6 @@
   return res;
 }
 
-static const grpc_slice_refcount_vtable stream_ref_slice_vtable = {
-    slice_stream_ref,            /* ref */
-    slice_stream_unref,          /* unref */
-    grpc_slice_default_eq_impl,  /* eq */
-    grpc_slice_default_hash_impl /* hash */
-};
-
 #ifndef NDEBUG
 void grpc_stream_ref_init(grpc_stream_refcount* refcount, int initial_refs,
                           grpc_iomgr_cb_func cb, void* cb_arg,
@@ -128,10 +88,12 @@
 void grpc_stream_ref_init(grpc_stream_refcount* refcount, int initial_refs,
                           grpc_iomgr_cb_func cb, void* cb_arg) {
 #endif
-  gpr_ref_init(&refcount->refs, initial_refs);
   GRPC_CLOSURE_INIT(&refcount->destroy, cb, cb_arg, grpc_schedule_on_exec_ctx);
-  refcount->slice_refcount.vtable = &stream_ref_slice_vtable;
-  refcount->slice_refcount.sub_refcount = &refcount->slice_refcount;
+
+  new (&refcount->refs) grpc_core::RefCount();
+  new (&refcount->slice_refcount) grpc_slice_refcount(
+      grpc_slice_refcount::Type::REGULAR, &refcount->refs, slice_stream_destroy,
+      refcount, &refcount->slice_refcount);
 }
 
 static void move64(uint64_t* from, uint64_t* to) {
@@ -162,7 +124,8 @@
 
 int grpc_transport_init_stream(grpc_transport* transport, grpc_stream* stream,
                                grpc_stream_refcount* refcount,
-                               const void* server_data, gpr_arena* arena) {
+                               const void* server_data,
+                               grpc_core::Arena* arena) {
   return transport->vtable->init_stream(transport, stream, refcount,
                                         server_data, arena);
 }
@@ -212,7 +175,7 @@
 // it's grpc_transport_stream_op_batch_finish_with_failure
 void grpc_transport_stream_op_batch_finish_with_failure(
     grpc_transport_stream_op_batch* batch, grpc_error* error,
-    grpc_call_combiner* call_combiner) {
+    grpc_core::CallCombiner* call_combiner) {
   if (batch->send_message) {
     batch->payload->send_message.send_message.reset();
   }
diff --git a/src/core/lib/transport/transport.h b/src/core/lib/transport/transport.h
index 8631a1a..a6a6e90 100644
--- a/src/core/lib/transport/transport.h
+++ b/src/core/lib/transport/transport.h
@@ -24,12 +24,13 @@
 #include <stddef.h>
 
 #include "src/core/lib/channel/context.h"
-#include "src/core/lib/gpr/arena.h"
+#include "src/core/lib/gprpp/arena.h"
 #include "src/core/lib/iomgr/call_combiner.h"
 #include "src/core/lib/iomgr/endpoint.h"
 #include "src/core/lib/iomgr/polling_entity.h"
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/pollset_set.h"
+#include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/transport/byte_stream.h"
 #include "src/core/lib/transport/metadata_batch.h"
 
@@ -51,7 +52,7 @@
 extern grpc_core::DebugOnlyTraceFlag grpc_trace_stream_refcount;
 
 typedef struct grpc_stream_refcount {
-  gpr_refcount refs;
+  grpc_core::RefCount refs;
   grpc_closure destroy;
 #ifndef NDEBUG
   const char* object_type;
@@ -63,19 +64,45 @@
 void grpc_stream_ref_init(grpc_stream_refcount* refcount, int initial_refs,
                           grpc_iomgr_cb_func cb, void* cb_arg,
                           const char* object_type);
-void grpc_stream_ref(grpc_stream_refcount* refcount, const char* reason);
-void grpc_stream_unref(grpc_stream_refcount* refcount, const char* reason);
 #define GRPC_STREAM_REF_INIT(rc, ir, cb, cb_arg, objtype) \
   grpc_stream_ref_init(rc, ir, cb, cb_arg, objtype)
 #else
 void grpc_stream_ref_init(grpc_stream_refcount* refcount, int initial_refs,
                           grpc_iomgr_cb_func cb, void* cb_arg);
-void grpc_stream_ref(grpc_stream_refcount* refcount);
-void grpc_stream_unref(grpc_stream_refcount* refcount);
 #define GRPC_STREAM_REF_INIT(rc, ir, cb, cb_arg, objtype) \
   grpc_stream_ref_init(rc, ir, cb, cb_arg)
 #endif
 
+#ifndef NDEBUG
+inline void grpc_stream_ref(grpc_stream_refcount* refcount,
+                            const char* reason) {
+  if (grpc_trace_stream_refcount.enabled()) {
+    gpr_log(GPR_DEBUG, "%s %p:%p REF %s", refcount->object_type, refcount,
+            refcount->destroy.cb_arg, reason);
+  }
+#else
+inline void grpc_stream_ref(grpc_stream_refcount* refcount) {
+#endif
+  refcount->refs.RefNonZero();
+}
+
+void grpc_stream_destroy(grpc_stream_refcount* refcount);
+
+#ifndef NDEBUG
+inline void grpc_stream_unref(grpc_stream_refcount* refcount,
+                              const char* reason) {
+  if (grpc_trace_stream_refcount.enabled()) {
+    gpr_log(GPR_DEBUG, "%s %p:%p UNREF %s", refcount->object_type, refcount,
+            refcount->destroy.cb_arg, reason);
+  }
+#else
+inline void grpc_stream_unref(grpc_stream_refcount* refcount) {
+#endif
+  if (GPR_UNLIKELY(refcount->refs.Unref())) {
+    grpc_stream_destroy(refcount);
+  }
+}
+
 /* Wrap a buffer that is owned by some stream object into a slice that shares
    the same refcount */
 grpc_slice grpc_slice_from_stream_owned_buffer(grpc_stream_refcount* refcount,
@@ -331,7 +358,8 @@
                    supplied from the accept_stream callback function */
 int grpc_transport_init_stream(grpc_transport* transport, grpc_stream* stream,
                                grpc_stream_refcount* refcount,
-                               const void* server_data, gpr_arena* arena);
+                               const void* server_data,
+                               grpc_core::Arena* arena);
 
 void grpc_transport_set_pops(grpc_transport* transport, grpc_stream* stream,
                              grpc_polling_entity* pollent);
@@ -352,7 +380,7 @@
 
 void grpc_transport_stream_op_batch_finish_with_failure(
     grpc_transport_stream_op_batch* op, grpc_error* error,
-    grpc_call_combiner* call_combiner);
+    grpc_core::CallCombiner* call_combiner);
 
 char* grpc_transport_stream_op_batch_string(grpc_transport_stream_op_batch* op);
 char* grpc_transport_op_string(grpc_transport_op* op);
diff --git a/src/core/lib/transport/transport_impl.h b/src/core/lib/transport/transport_impl.h
index ba5e05d..526cc1b 100644
--- a/src/core/lib/transport/transport_impl.h
+++ b/src/core/lib/transport/transport_impl.h
@@ -34,7 +34,7 @@
   /* implementation of grpc_transport_init_stream */
   int (*init_stream)(grpc_transport* self, grpc_stream* stream,
                      grpc_stream_refcount* refcount, const void* server_data,
-                     gpr_arena* arena);
+                     grpc_core::Arena* arena);
 
   /* implementation of grpc_transport_set_pollset */
   void (*set_pollset)(grpc_transport* self, grpc_stream* stream,
diff --git a/src/core/tsi/fake_transport_security.cc b/src/core/tsi/fake_transport_security.cc
index 4d4c495..fde88dd 100644
--- a/src/core/tsi/fake_transport_security.cc
+++ b/src/core/tsi/fake_transport_security.cc
@@ -585,7 +585,7 @@
     if (next_message_to_send > TSI_FAKE_HANDSHAKE_MESSAGE_MAX) {
       next_message_to_send = TSI_FAKE_HANDSHAKE_MESSAGE_MAX;
     }
-    if (tsi_tracing_enabled.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(tsi_tracing_enabled)) {
       gpr_log(GPR_INFO, "%s prepared %s.",
               impl->is_client ? "Client" : "Server",
               tsi_fake_handshake_message_to_string(impl->next_message_to_send));
@@ -597,7 +597,7 @@
   if (!impl->is_client &&
       impl->next_message_to_send == TSI_FAKE_HANDSHAKE_MESSAGE_MAX) {
     /* We're done. */
-    if (tsi_tracing_enabled.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(tsi_tracing_enabled)) {
       gpr_log(GPR_INFO, "Server is done.");
     }
     impl->result = TSI_OK;
@@ -636,7 +636,7 @@
             tsi_fake_handshake_message_to_string(received_msg),
             tsi_fake_handshake_message_to_string(expected_msg));
   }
-  if (tsi_tracing_enabled.enabled()) {
+  if (GRPC_TRACE_FLAG_ENABLED(tsi_tracing_enabled)) {
     gpr_log(GPR_INFO, "%s received %s.", impl->is_client ? "Client" : "Server",
             tsi_fake_handshake_message_to_string(received_msg));
   }
@@ -644,7 +644,7 @@
   impl->needs_incoming_message = 0;
   if (impl->next_message_to_send == TSI_FAKE_HANDSHAKE_MESSAGE_MAX) {
     /* We're done. */
-    if (tsi_tracing_enabled.enabled()) {
+    if (GRPC_TRACE_FLAG_ENABLED(tsi_tracing_enabled)) {
       gpr_log(GPR_INFO, "%s is done.", impl->is_client ? "Client" : "Server");
     }
     impl->result = TSI_OK;
diff --git a/src/core/tsi/ssl/session_cache/ssl_session_cache.cc b/src/core/tsi/ssl/session_cache/ssl_session_cache.cc
index f9184bc..ba0745a 100644
--- a/src/core/tsi/ssl/session_cache/ssl_session_cache.cc
+++ b/src/core/tsi/ssl/session_cache/ssl_session_cache.cc
@@ -18,7 +18,7 @@
 
 #include <grpc/support/port_platform.h>
 
-#include "src/core/lib/gprpp/mutex_lock.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/tsi/ssl/session_cache/ssl_session.h"
 #include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
diff --git a/src/core/tsi/ssl_transport_security.cc b/src/core/tsi/ssl_transport_security.cc
index cbdb422..25ae2ce 100644
--- a/src/core/tsi/ssl_transport_security.cc
+++ b/src/core/tsi/ssl_transport_security.cc
@@ -213,7 +213,7 @@
 /* TODO(jboeuf): Remove when we are past the debugging phase with this code. */
 static void ssl_log_where_info(const SSL* ssl, int where, int flag,
                                const char* msg) {
-  if ((where & flag) && tsi_tracing_enabled.enabled()) {
+  if ((where & flag) && GRPC_TRACE_FLAG_ENABLED(tsi_tracing_enabled)) {
     gpr_log(GPR_INFO, "%20.20s - %30.30s  - %5.10s", msg,
             SSL_state_string_long(ssl), SSL_state_string(ssl));
   }
diff --git a/src/cpp/client/channel_cc.cc b/src/cpp/client/channel_cc.cc
index db59d4d..58f012d 100644
--- a/src/cpp/client/channel_cc.cc
+++ b/src/cpp/client/channel_cc.cc
@@ -42,14 +42,18 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/surface/completion_queue.h"
 
-namespace grpc {
+void grpc::experimental::ChannelResetConnectionBackoff(
+    ::grpc::Channel* channel) {
+  grpc_impl::experimental::ChannelResetConnectionBackoff(channel);
+}
 
-static internal::GrpcLibraryInitializer g_gli_initializer;
-Channel::Channel(
-    const grpc::string& host, grpc_channel* channel,
-    std::vector<
-        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-        interceptor_creators)
+namespace grpc_impl {
+
+static ::grpc::internal::GrpcLibraryInitializer g_gli_initializer;
+Channel::Channel(const grpc::string& host, grpc_channel* channel,
+                 std::vector<std::unique_ptr<
+                     ::grpc::experimental::ClientInterceptorFactoryInterface>>
+                     interceptor_creators)
     : host_(host), c_channel_(channel) {
   interceptor_creators_ = std::move(interceptor_creators);
   g_gli_initializer.summon();
@@ -65,7 +69,8 @@
 namespace {
 
 inline grpc_slice SliceFromArray(const char* arr, size_t len) {
-  return g_core_codegen_interface->grpc_slice_from_copied_buffer(arr, len);
+  return ::grpc::g_core_codegen_interface->grpc_slice_from_copied_buffer(arr,
+                                                                         len);
 }
 
 grpc::string GetChannelInfoField(grpc_channel* channel,
@@ -103,10 +108,9 @@
 
 }  // namespace experimental
 
-internal::Call Channel::CreateCallInternal(const internal::RpcMethod& method,
-                                           ClientContext* context,
-                                           CompletionQueue* cq,
-                                           size_t interceptor_pos) {
+::grpc::internal::Call Channel::CreateCallInternal(
+    const ::grpc::internal::RpcMethod& method, ::grpc::ClientContext* context,
+    ::grpc::CompletionQueue* cq, size_t interceptor_pos) {
   const bool kRegistered = method.channel_tag() && context->authority().empty();
   grpc_call* c_call = nullptr;
   if (kRegistered) {
@@ -115,7 +119,7 @@
         context->propagation_options_.c_bitmask(), cq->cq(),
         method.channel_tag(), context->raw_deadline(), nullptr);
   } else {
-    const string* host_str = nullptr;
+    const ::grpc::string* host_str = nullptr;
     if (!context->authority_.empty()) {
       host_str = &context->authority_;
     } else if (!host_.empty()) {
@@ -125,7 +129,7 @@
         SliceFromArray(method.name(), strlen(method.name()));
     grpc_slice host_slice;
     if (host_str != nullptr) {
-      host_slice = SliceFromCopiedString(*host_str);
+      host_slice = ::grpc::SliceFromCopiedString(*host_str);
     }
     c_call = grpc_channel_create_call(
         c_channel_, context->propagate_from_call_,
@@ -147,17 +151,17 @@
                                    interceptor_creators_, interceptor_pos);
   context->set_call(c_call, shared_from_this());
 
-  return internal::Call(c_call, this, cq, info);
+  return ::grpc::internal::Call(c_call, this, cq, info);
 }
 
-internal::Call Channel::CreateCall(const internal::RpcMethod& method,
-                                   ClientContext* context,
-                                   CompletionQueue* cq) {
+::grpc::internal::Call Channel::CreateCall(
+    const ::grpc::internal::RpcMethod& method, ::grpc::ClientContext* context,
+    ::grpc::CompletionQueue* cq) {
   return CreateCallInternal(method, context, cq, 0);
 }
 
-void Channel::PerformOpsOnCall(internal::CallOpSetInterface* ops,
-                               internal::Call* call) {
+void Channel::PerformOpsOnCall(::grpc::internal::CallOpSetInterface* ops,
+                               ::grpc::internal::Call* call) {
   ops->FillOps(
       call);  // Make a copy of call. It's fine since Call just has pointers
 }
@@ -173,7 +177,7 @@
 
 namespace {
 
-class TagSaver final : public internal::CompletionQueueTag {
+class TagSaver final : public ::grpc::internal::CompletionQueueTag {
  public:
   explicit TagSaver(void* tag) : tag_(tag) {}
   ~TagSaver() override {}
@@ -191,7 +195,7 @@
 
 void Channel::NotifyOnStateChangeImpl(grpc_connectivity_state last_observed,
                                       gpr_timespec deadline,
-                                      CompletionQueue* cq, void* tag) {
+                                      ::grpc::CompletionQueue* cq, void* tag) {
   TagSaver* tag_saver = new TagSaver(tag);
   grpc_channel_watch_connectivity_state(c_channel_, last_observed, deadline,
                                         cq->cq(), tag_saver);
@@ -199,7 +203,7 @@
 
 bool Channel::WaitForStateChangeImpl(grpc_connectivity_state last_observed,
                                      gpr_timespec deadline) {
-  CompletionQueue cq;
+  ::grpc::CompletionQueue cq;
   bool ok = false;
   void* tag = nullptr;
   NotifyOnStateChangeImpl(last_observed, deadline, &cq, nullptr);
@@ -214,7 +218,7 @@
   ShutdownCallback() { functor_run = &ShutdownCallback::Run; }
   // TakeCQ takes ownership of the cq into the shutdown callback
   // so that the shutdown callback will be responsible for destroying it
-  void TakeCQ(CompletionQueue* cq) { cq_ = cq; }
+  void TakeCQ(::grpc::CompletionQueue* cq) { cq_ = cq; }
 
   // The Run function will get invoked by the completion queue library
   // when the shutdown is actually complete
@@ -225,17 +229,17 @@
   }
 
  private:
-  CompletionQueue* cq_ = nullptr;
+  ::grpc::CompletionQueue* cq_ = nullptr;
 };
 }  // namespace
 
-CompletionQueue* Channel::CallbackCQ() {
+::grpc::CompletionQueue* Channel::CallbackCQ() {
   // TODO(vjpai): Consider using a single global CQ for the default CQ
   // if there is no explicit per-channel CQ registered
-  std::lock_guard<std::mutex> l(mu_);
+  grpc::internal::MutexLock l(&mu_);
   if (callback_cq_ == nullptr) {
     auto* shutdown_callback = new ShutdownCallback;
-    callback_cq_ = new CompletionQueue(grpc_completion_queue_attributes{
+    callback_cq_ = new ::grpc::CompletionQueue(grpc_completion_queue_attributes{
         GRPC_CQ_CURRENT_VERSION, GRPC_CQ_CALLBACK, GRPC_CQ_DEFAULT_POLLING,
         shutdown_callback});
 
@@ -245,4 +249,4 @@
   return callback_cq_;
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/client/client_context.cc b/src/cpp/client/client_context.cc
index efb59c7..0ae1ecb 100644
--- a/src/cpp/client/client_context.cc
+++ b/src/cpp/client/client_context.cc
@@ -25,11 +25,17 @@
 #include <grpc/support/string_util.h>
 
 #include <grpcpp/impl/codegen/interceptor_common.h>
+#include <grpcpp/impl/codegen/sync.h>
 #include <grpcpp/impl/grpc_library.h>
 #include <grpcpp/security/credentials.h>
 #include <grpcpp/server_context.h>
 #include <grpcpp/support/time.h>
 
+namespace grpc_impl {
+
+class Channel;
+}
+
 namespace grpc {
 
 class DefaultGlobalClientCallbacks final
@@ -82,9 +88,9 @@
   send_initial_metadata_.insert(std::make_pair(meta_key, meta_value));
 }
 
-void ClientContext::set_call(grpc_call* call,
-                             const std::shared_ptr<Channel>& channel) {
-  std::unique_lock<std::mutex> lock(mu_);
+void ClientContext::set_call(
+    grpc_call* call, const std::shared_ptr<::grpc_impl::Channel>& channel) {
+  grpc::internal::MutexLock lock(&mu_);
   GPR_ASSERT(call_ == nullptr);
   call_ = call;
   channel_ = channel;
@@ -114,7 +120,7 @@
 }
 
 void ClientContext::TryCancel() {
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc::internal::MutexLock lock(&mu_);
   if (call_) {
     SendCancelToInterceptors();
     grpc_call_cancel(call_, nullptr);
diff --git a/src/cpp/client/create_channel.cc b/src/cpp/client/create_channel.cc
index 457daa6..3318ded 100644
--- a/src/cpp/client/create_channel.cc
+++ b/src/cpp/client/create_channel.cc
@@ -21,32 +21,33 @@
 #include <grpcpp/channel.h>
 #include <grpcpp/create_channel.h>
 #include <grpcpp/impl/grpc_library.h>
+#include <grpcpp/security/credentials.h>
 #include <grpcpp/support/channel_arguments.h>
 
 #include "src/cpp/client/create_channel_internal.h"
 
-namespace grpc {
-class ChannelArguments;
-
-std::shared_ptr<Channel> CreateChannel(
+namespace grpc_impl {
+std::shared_ptr<grpc::Channel> CreateChannelImpl(
     const grpc::string& target,
-    const std::shared_ptr<ChannelCredentials>& creds) {
-  return CreateCustomChannel(target, creds, ChannelArguments());
+    const std::shared_ptr<grpc::ChannelCredentials>& creds) {
+  return CreateCustomChannelImpl(target, creds, grpc::ChannelArguments());
 }
 
-std::shared_ptr<Channel> CreateCustomChannel(
+std::shared_ptr<grpc::Channel> CreateCustomChannelImpl(
     const grpc::string& target,
-    const std::shared_ptr<ChannelCredentials>& creds,
-    const ChannelArguments& args) {
-  GrpcLibraryCodegen init_lib;  // We need to call init in case of a bad creds.
-  return creds ? creds->CreateChannel(target, args)
-               : CreateChannelInternal(
+    const std::shared_ptr<grpc::ChannelCredentials>& creds,
+    const grpc::ChannelArguments& args) {
+  grpc::GrpcLibraryCodegen
+      init_lib;  // We need to call init in case of a bad creds.
+  return creds ? creds->CreateChannelImpl(target, args)
+               : grpc::CreateChannelInternal(
                      "",
                      grpc_lame_client_channel_create(
                          nullptr, GRPC_STATUS_INVALID_ARGUMENT,
                          "Invalid credentials."),
                      std::vector<std::unique_ptr<
-                         experimental::ClientInterceptorFactoryInterface>>());
+                         grpc::experimental::
+                             ClientInterceptorFactoryInterface>>());
 }
 
 namespace experimental {
@@ -61,23 +62,22 @@
 /// hold an object or is invalid, a lame channel (one on which all operations
 /// fail) is returned.
 /// \param args Options for channel creation.
-std::shared_ptr<Channel> CreateCustomChannelWithInterceptors(
+std::shared_ptr<grpc::Channel> CreateCustomChannelWithInterceptors(
     const grpc::string& target,
-    const std::shared_ptr<ChannelCredentials>& creds,
-    const ChannelArguments& args,
+    const std::shared_ptr<grpc::ChannelCredentials>& creds,
+    const grpc::ChannelArguments& args,
     std::vector<
-        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+        std::unique_ptr<grpc::experimental::ClientInterceptorFactoryInterface>>
         interceptor_creators) {
   return creds ? creds->CreateChannelWithInterceptors(
                      target, args, std::move(interceptor_creators))
-               : CreateChannelInternal(
+               : ::grpc::CreateChannelInternal(
                      "",
                      grpc_lame_client_channel_create(
                          nullptr, GRPC_STATUS_INVALID_ARGUMENT,
                          "Invalid credentials."),
-                     std::vector<std::unique_ptr<
-                         experimental::ClientInterceptorFactoryInterface>>());
+                     std::move(interceptor_creators));
 }
 }  // namespace experimental
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/client/create_channel_internal.cc b/src/cpp/client/create_channel_internal.cc
index a0efb97..63e1d14 100644
--- a/src/cpp/client/create_channel_internal.cc
+++ b/src/cpp/client/create_channel_internal.cc
@@ -26,10 +26,11 @@
 
 std::shared_ptr<Channel> CreateChannelInternal(
     const grpc::string& host, grpc_channel* c_channel,
-    std::vector<
-        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+    std::vector<std::unique_ptr<
+        ::grpc::experimental::ClientInterceptorFactoryInterface>>
         interceptor_creators) {
   return std::shared_ptr<Channel>(
       new Channel(host, c_channel, std::move(interceptor_creators)));
 }
+
 }  // namespace grpc
diff --git a/src/cpp/client/create_channel_internal.h b/src/cpp/client/create_channel_internal.h
index a90c92c..3b201af 100644
--- a/src/cpp/client/create_channel_internal.h
+++ b/src/cpp/client/create_channel_internal.h
@@ -27,12 +27,11 @@
 struct grpc_channel;
 
 namespace grpc {
-class Channel;
 
 std::shared_ptr<Channel> CreateChannelInternal(
     const grpc::string& host, grpc_channel* c_channel,
-    std::vector<
-        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+    std::vector<std::unique_ptr<
+        ::grpc::experimental::ClientInterceptorFactoryInterface>>
         interceptor_creators);
 
 }  // namespace grpc
diff --git a/src/cpp/client/create_channel_posix.cc b/src/cpp/client/create_channel_posix.cc
index 6de3735..ca26c3f 100644
--- a/src/cpp/client/create_channel_posix.cc
+++ b/src/cpp/client/create_channel_posix.cc
@@ -19,20 +19,22 @@
 #include <grpc/grpc.h>
 #include <grpc/grpc_posix.h>
 #include <grpcpp/channel.h>
-#include <grpcpp/create_channel.h>
 #include <grpcpp/impl/grpc_library.h>
+#include <grpcpp/support/channel_arguments.h>
 
 #include "src/cpp/client/create_channel_internal.h"
 
 namespace grpc_impl {
 
+class ChannelArguments;
+
 #ifdef GPR_SUPPORT_CHANNELS_FROM_FD
 
 std::shared_ptr<grpc::Channel> CreateInsecureChannelFromFd(
     const grpc::string& target, int fd) {
   grpc::internal::GrpcLibrary init_lib;
   init_lib.init();
-  return grpc::CreateChannelInternal(
+  return ::grpc::CreateChannelInternal(
       "", grpc_insecure_channel_create_from_fd(target.c_str(), fd, nullptr),
       std::vector<std::unique_ptr<
           grpc::experimental::ClientInterceptorFactoryInterface>>());
@@ -44,7 +46,7 @@
   init_lib.init();
   grpc_channel_args channel_args;
   args.SetChannelArgs(&channel_args);
-  return grpc::CreateChannelInternal(
+  return ::grpc::CreateChannelInternal(
       "",
       grpc_insecure_channel_create_from_fd(target.c_str(), fd, &channel_args),
       std::vector<std::unique_ptr<
@@ -63,7 +65,7 @@
   init_lib.init();
   grpc_channel_args channel_args;
   args.SetChannelArgs(&channel_args);
-  return grpc::CreateChannelInternal(
+  return ::grpc::CreateChannelInternal(
       "",
       grpc_insecure_channel_create_from_fd(target.c_str(), fd, &channel_args),
       std::move(interceptor_creators));
diff --git a/src/cpp/client/credentials_cc.cc b/src/cpp/client/credentials_cc.cc
index 2a0f06f..62334bd 100644
--- a/src/cpp/client/credentials_cc.cc
+++ b/src/cpp/client/credentials_cc.cc
@@ -19,9 +19,9 @@
 #include <grpcpp/impl/grpc_library.h>
 #include <grpcpp/security/credentials.h>
 
-namespace grpc {
+namespace grpc_impl {
 
-static internal::GrpcLibraryInitializer g_gli_initializer;
+static grpc::internal::GrpcLibraryInitializer g_gli_initializer;
 ChannelCredentials::ChannelCredentials() { g_gli_initializer.summon(); }
 
 ChannelCredentials::~ChannelCredentials() {}
@@ -30,4 +30,4 @@
 
 CallCredentials::~CallCredentials() {}
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/client/cronet_credentials.cc b/src/cpp/client/cronet_credentials.cc
index b280176..f4ead14 100644
--- a/src/cpp/client/cronet_credentials.cc
+++ b/src/cpp/client/cronet_credentials.cc
@@ -29,7 +29,7 @@
  public:
   CronetChannelCredentialsImpl(void* engine) : engine_(engine) {}
 
-  std::shared_ptr<grpc::Channel> CreateChannel(
+  std::shared_ptr<grpc::Channel> CreateChannelImpl(
       const string& target, const grpc::ChannelArguments& args) override {
     return CreateChannelWithInterceptors(
         target, args,
@@ -55,10 +55,10 @@
   }
   void* engine_;
 };
-
+}  // namespace grpc
+namespace grpc_impl {
 std::shared_ptr<ChannelCredentials> CronetChannelCredentials(void* engine) {
   return std::shared_ptr<ChannelCredentials>(
-      new CronetChannelCredentialsImpl(engine));
+      new grpc::CronetChannelCredentialsImpl(engine));
 }
-
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/client/generic_stub.cc b/src/cpp/client/generic_stub.cc
index f61c1b5..41631c7 100644
--- a/src/cpp/client/generic_stub.cc
+++ b/src/cpp/client/generic_stub.cc
@@ -22,63 +22,75 @@
 #include <grpcpp/impl/rpc_method.h>
 #include <grpcpp/support/client_callback.h>
 
-namespace grpc {
+namespace grpc_impl {
 
 namespace {
-std::unique_ptr<GenericClientAsyncReaderWriter> CallInternal(
-    ChannelInterface* channel, ClientContext* context,
-    const grpc::string& method, CompletionQueue* cq, bool start, void* tag) {
-  return std::unique_ptr<GenericClientAsyncReaderWriter>(
-      internal::ClientAsyncReaderWriterFactory<ByteBuffer, ByteBuffer>::Create(
-          channel, cq,
-          internal::RpcMethod(method.c_str(),
-                              internal::RpcMethod::BIDI_STREAMING),
-          context, start, tag));
+std::unique_ptr<grpc::GenericClientAsyncReaderWriter> CallInternal(
+    grpc::ChannelInterface* channel, grpc::ClientContext* context,
+    const grpc::string& method, grpc::CompletionQueue* cq, bool start,
+    void* tag) {
+  return std::unique_ptr<grpc::GenericClientAsyncReaderWriter>(
+      grpc::internal::ClientAsyncReaderWriterFactory<grpc::ByteBuffer,
+                                                     grpc::ByteBuffer>::
+          Create(channel, cq,
+                 grpc::internal::RpcMethod(
+                     method.c_str(), grpc::internal::RpcMethod::BIDI_STREAMING),
+                 context, start, tag));
 }
 
 }  // namespace
 
 // begin a call to a named method
-std::unique_ptr<GenericClientAsyncReaderWriter> GenericStub::Call(
-    ClientContext* context, const grpc::string& method, CompletionQueue* cq,
-    void* tag) {
+std::unique_ptr<grpc::GenericClientAsyncReaderWriter> GenericStub::Call(
+    grpc::ClientContext* context, const grpc::string& method,
+    grpc::CompletionQueue* cq, void* tag) {
   return CallInternal(channel_.get(), context, method, cq, true, tag);
 }
 
 // setup a call to a named method
-std::unique_ptr<GenericClientAsyncReaderWriter> GenericStub::PrepareCall(
-    ClientContext* context, const grpc::string& method, CompletionQueue* cq) {
+std::unique_ptr<grpc::GenericClientAsyncReaderWriter> GenericStub::PrepareCall(
+    grpc::ClientContext* context, const grpc::string& method,
+    grpc::CompletionQueue* cq) {
   return CallInternal(channel_.get(), context, method, cq, false, nullptr);
 }
 
 // setup a unary call to a named method
-std::unique_ptr<GenericClientAsyncResponseReader> GenericStub::PrepareUnaryCall(
-    ClientContext* context, const grpc::string& method,
-    const ByteBuffer& request, CompletionQueue* cq) {
-  return std::unique_ptr<GenericClientAsyncResponseReader>(
-      internal::ClientAsyncResponseReaderFactory<ByteBuffer>::Create(
-          channel_.get(), cq,
-          internal::RpcMethod(method.c_str(), internal::RpcMethod::NORMAL_RPC),
-          context, request, false));
+std::unique_ptr<grpc::GenericClientAsyncResponseReader>
+GenericStub::PrepareUnaryCall(grpc::ClientContext* context,
+                              const grpc::string& method,
+                              const grpc::ByteBuffer& request,
+                              grpc::CompletionQueue* cq) {
+  return std::unique_ptr<grpc::GenericClientAsyncResponseReader>(
+      grpc::internal::ClientAsyncResponseReaderFactory<
+          grpc::ByteBuffer>::Create(channel_.get(), cq,
+                                    grpc::internal::RpcMethod(
+                                        method.c_str(),
+                                        grpc::internal::RpcMethod::NORMAL_RPC),
+                                    context, request, false));
 }
 
 void GenericStub::experimental_type::UnaryCall(
-    ClientContext* context, const grpc::string& method,
-    const ByteBuffer* request, ByteBuffer* response,
-    std::function<void(Status)> on_completion) {
-  internal::CallbackUnaryCall(
+    grpc::ClientContext* context, const grpc::string& method,
+    const grpc::ByteBuffer* request, grpc::ByteBuffer* response,
+    std::function<void(grpc::Status)> on_completion) {
+  grpc::internal::CallbackUnaryCall(
       stub_->channel_.get(),
-      internal::RpcMethod(method.c_str(), internal::RpcMethod::NORMAL_RPC),
+      grpc::internal::RpcMethod(method.c_str(),
+                                grpc::internal::RpcMethod::NORMAL_RPC),
       context, request, response, std::move(on_completion));
 }
 
 void GenericStub::experimental_type::PrepareBidiStreamingCall(
-    ClientContext* context, const grpc::string& method,
-    experimental::ClientBidiReactor<ByteBuffer, ByteBuffer>* reactor) {
-  internal::ClientCallbackReaderWriterFactory<ByteBuffer, ByteBuffer>::Create(
-      stub_->channel_.get(),
-      internal::RpcMethod(method.c_str(), internal::RpcMethod::BIDI_STREAMING),
-      context, reactor);
+    grpc::ClientContext* context, const grpc::string& method,
+    grpc::experimental::ClientBidiReactor<grpc::ByteBuffer, grpc::ByteBuffer>*
+        reactor) {
+  grpc::internal::ClientCallbackReaderWriterFactory<
+      grpc::ByteBuffer,
+      grpc::ByteBuffer>::Create(stub_->channel_.get(),
+                                grpc::internal::RpcMethod(
+                                    method.c_str(),
+                                    grpc::internal::RpcMethod::BIDI_STREAMING),
+                                context, reactor);
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/client/insecure_credentials.cc b/src/cpp/client/insecure_credentials.cc
index 241ce91..dcbb56d 100644
--- a/src/cpp/client/insecure_credentials.cc
+++ b/src/cpp/client/insecure_credentials.cc
@@ -21,31 +21,32 @@
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
 #include <grpcpp/channel.h>
+#include <grpcpp/security/credentials.h>
 #include <grpcpp/support/channel_arguments.h>
 #include <grpcpp/support/config.h>
 #include "src/cpp/client/create_channel_internal.h"
 
-namespace grpc {
+namespace grpc_impl {
 
 namespace {
 class InsecureChannelCredentialsImpl final : public ChannelCredentials {
  public:
-  std::shared_ptr<grpc::Channel> CreateChannel(
-      const string& target, const grpc::ChannelArguments& args) override {
+  std::shared_ptr<::grpc::Channel> CreateChannelImpl(
+      const grpc::string& target, const grpc::ChannelArguments& args) override {
     return CreateChannelWithInterceptors(
         target, args,
         std::vector<std::unique_ptr<
-            experimental::ClientInterceptorFactoryInterface>>());
+            grpc::experimental::ClientInterceptorFactoryInterface>>());
   }
 
-  std::shared_ptr<grpc::Channel> CreateChannelWithInterceptors(
-      const string& target, const grpc::ChannelArguments& args,
-      std::vector<
-          std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+  std::shared_ptr<::grpc::Channel> CreateChannelWithInterceptors(
+      const grpc::string& target, const grpc::ChannelArguments& args,
+      std::vector<std::unique_ptr<
+          grpc::experimental::ClientInterceptorFactoryInterface>>
           interceptor_creators) override {
     grpc_channel_args channel_args;
     args.SetChannelArgs(&channel_args);
-    return CreateChannelInternal(
+    return ::grpc::CreateChannelInternal(
         "",
         grpc_insecure_channel_create(target.c_str(), &channel_args, nullptr),
         std::move(interceptor_creators));
@@ -60,4 +61,4 @@
       new InsecureChannelCredentialsImpl());
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc
index 4d0ed35..724a43a 100644
--- a/src/cpp/client/secure_credentials.cc
+++ b/src/cpp/client/secure_credentials.cc
@@ -25,32 +25,32 @@
 #include "src/cpp/client/create_channel_internal.h"
 #include "src/cpp/common/secure_auth_context.h"
 
-namespace grpc {
+namespace grpc_impl {
 
-static internal::GrpcLibraryInitializer g_gli_initializer;
+static grpc::internal::GrpcLibraryInitializer g_gli_initializer;
 SecureChannelCredentials::SecureChannelCredentials(
     grpc_channel_credentials* c_creds)
     : c_creds_(c_creds) {
   g_gli_initializer.summon();
 }
 
-std::shared_ptr<grpc::Channel> SecureChannelCredentials::CreateChannel(
-    const string& target, const grpc::ChannelArguments& args) {
+std::shared_ptr<grpc::Channel> SecureChannelCredentials::CreateChannelImpl(
+    const grpc::string& target, const grpc::ChannelArguments& args) {
   return CreateChannelWithInterceptors(
       target, args,
-      std::vector<
-          std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>());
+      std::vector<std::unique_ptr<
+          grpc::experimental::ClientInterceptorFactoryInterface>>());
 }
 
 std::shared_ptr<grpc::Channel>
 SecureChannelCredentials::CreateChannelWithInterceptors(
-    const string& target, const grpc::ChannelArguments& args,
+    const grpc::string& target, const grpc::ChannelArguments& args,
     std::vector<
-        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+        std::unique_ptr<grpc::experimental::ClientInterceptorFactoryInterface>>
         interceptor_creators) {
   grpc_channel_args channel_args;
   args.SetChannelArgs(&channel_args);
-  return CreateChannelInternal(
+  return ::grpc::CreateChannelInternal(
       args.GetSslTargetNameOverride(),
       grpc_secure_channel_create(c_creds_, target.c_str(), &channel_args,
                                  nullptr),
@@ -83,14 +83,14 @@
 }  // namespace
 
 std::shared_ptr<ChannelCredentials> GoogleDefaultCredentials() {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   return WrapChannelCredentials(grpc_google_default_credentials_create());
 }
 
 // Builds SSL Credentials given SSL specific options
 std::shared_ptr<ChannelCredentials> SslCredentials(
     const SslCredentialsOptions& options) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {
       options.pem_private_key.c_str(), options.pem_cert_chain.c_str()};
 
@@ -106,7 +106,7 @@
 // Builds ALTS Credentials given ALTS specific options
 std::shared_ptr<ChannelCredentials> AltsCredentials(
     const AltsCredentialsOptions& options) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   grpc_alts_credentials_options* c_options =
       grpc_alts_credentials_client_options_create();
   for (auto service_account = options.target_service_accounts.begin();
@@ -123,7 +123,7 @@
 // Builds Local Credentials
 std::shared_ptr<ChannelCredentials> LocalCredentials(
     grpc_local_connect_type type) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   return WrapChannelCredentials(grpc_local_credentials_create(type));
 }
 
@@ -131,7 +131,7 @@
 
 // Builds credentials for use when running in GCE
 std::shared_ptr<CallCredentials> GoogleComputeEngineCredentials() {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   return WrapCallCredentials(
       grpc_google_compute_engine_credentials_create(nullptr));
 }
@@ -139,7 +139,7 @@
 // Builds JWT credentials.
 std::shared_ptr<CallCredentials> ServiceAccountJWTAccessCredentials(
     const grpc::string& json_key, long token_lifetime_seconds) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   if (token_lifetime_seconds <= 0) {
     gpr_log(GPR_ERROR,
             "Trying to create JWTCredentials with non-positive lifetime");
@@ -154,7 +154,7 @@
 // Builds refresh token credentials.
 std::shared_ptr<CallCredentials> GoogleRefreshTokenCredentials(
     const grpc::string& json_refresh_token) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   return WrapCallCredentials(grpc_google_refresh_token_credentials_create(
       json_refresh_token.c_str(), nullptr));
 }
@@ -162,7 +162,7 @@
 // Builds access token credentials.
 std::shared_ptr<CallCredentials> AccessTokenCredentials(
     const grpc::string& access_token) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   return WrapCallCredentials(
       grpc_access_token_credentials_create(access_token.c_str(), nullptr));
 }
@@ -171,7 +171,7 @@
 std::shared_ptr<CallCredentials> GoogleIAMCredentials(
     const grpc::string& authorization_token,
     const grpc::string& authority_selector) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
   return WrapCallCredentials(grpc_google_iam_credentials_create(
       authorization_token.c_str(), authority_selector.c_str(), nullptr));
 }
@@ -207,6 +207,23 @@
   return nullptr;
 }
 
+std::shared_ptr<grpc_impl::CallCredentials> MetadataCredentialsFromPlugin(
+    std::unique_ptr<MetadataCredentialsPlugin> plugin) {
+  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
+  const char* type = plugin->GetType();
+  grpc::MetadataCredentialsPluginWrapper* wrapper =
+      new grpc::MetadataCredentialsPluginWrapper(std::move(plugin));
+  grpc_metadata_credentials_plugin c_plugin = {
+      grpc::MetadataCredentialsPluginWrapper::GetMetadata,
+      grpc::MetadataCredentialsPluginWrapper::Destroy, wrapper, type};
+  return WrapCallCredentials(
+      grpc_metadata_credentials_create_from_plugin(c_plugin, nullptr));
+}
+
+}  // namespace grpc_impl
+
+namespace grpc {
+
 void MetadataCredentialsPluginWrapper::Destroy(void* wrapper) {
   if (wrapper == nullptr) return;
   MetadataCredentialsPluginWrapper* w =
@@ -308,17 +325,4 @@
     std::unique_ptr<MetadataCredentialsPlugin> plugin)
     : thread_pool_(CreateDefaultThreadPool()), plugin_(std::move(plugin)) {}
 
-std::shared_ptr<CallCredentials> MetadataCredentialsFromPlugin(
-    std::unique_ptr<MetadataCredentialsPlugin> plugin) {
-  GrpcLibraryCodegen init;  // To call grpc_init().
-  const char* type = plugin->GetType();
-  MetadataCredentialsPluginWrapper* wrapper =
-      new MetadataCredentialsPluginWrapper(std::move(plugin));
-  grpc_metadata_credentials_plugin c_plugin = {
-      MetadataCredentialsPluginWrapper::GetMetadata,
-      MetadataCredentialsPluginWrapper::Destroy, wrapper, type};
-  return WrapCallCredentials(
-      grpc_metadata_credentials_create_from_plugin(c_plugin, nullptr));
-}
-
 }  // namespace grpc
diff --git a/src/cpp/client/secure_credentials.h b/src/cpp/client/secure_credentials.h
index 4918bd5..c4eef6c 100644
--- a/src/cpp/client/secure_credentials.h
+++ b/src/cpp/client/secure_credentials.h
@@ -27,7 +27,9 @@
 #include "src/core/lib/security/credentials/credentials.h"
 #include "src/cpp/server/thread_pool_interface.h"
 
-namespace grpc {
+namespace grpc_impl {
+
+class Channel;
 
 class SecureChannelCredentials final : public ChannelCredentials {
  public:
@@ -37,16 +39,16 @@
   }
   grpc_channel_credentials* GetRawCreds() { return c_creds_; }
 
-  std::shared_ptr<grpc::Channel> CreateChannel(
-      const string& target, const grpc::ChannelArguments& args) override;
+  std::shared_ptr<::grpc::Channel> CreateChannelImpl(
+      const grpc::string& target, const grpc::ChannelArguments& args) override;
 
   SecureChannelCredentials* AsSecureCredentials() override { return this; }
 
  private:
-  std::shared_ptr<grpc::Channel> CreateChannelWithInterceptors(
-      const string& target, const grpc::ChannelArguments& args,
-      std::vector<
-          std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+  std::shared_ptr<::grpc::Channel> CreateChannelWithInterceptors(
+      const grpc::string& target, const grpc::ChannelArguments& args,
+      std::vector<std::unique_ptr<
+          ::grpc::experimental::ClientInterceptorFactoryInterface>>
           interceptor_creators) override;
   grpc_channel_credentials* const c_creds_;
 };
@@ -66,6 +68,10 @@
   grpc_call_credentials* const c_creds_;
 };
 
+}  // namespace grpc_impl
+
+namespace grpc {
+
 class MetadataCredentialsPluginWrapper final : private GrpcLibraryCodegen {
  public:
   static void Destroy(void* wrapper);
diff --git a/src/cpp/common/channel_arguments.cc b/src/cpp/common/channel_arguments.cc
index 214d72f..9321398 100644
--- a/src/cpp/common/channel_arguments.cc
+++ b/src/cpp/common/channel_arguments.cc
@@ -27,11 +27,11 @@
 #include "src/core/lib/iomgr/exec_ctx.h"
 #include "src/core/lib/iomgr/socket_mutator.h"
 
-namespace grpc {
+namespace grpc_impl {
 
 ChannelArguments::ChannelArguments() {
   // This will be ignored if used on the server side.
-  SetString(GRPC_ARG_PRIMARY_USER_AGENT_STRING, "grpc-c++/" + Version());
+  SetString(GRPC_ARG_PRIMARY_USER_AGENT_STRING, "grpc-c++/" + grpc::Version());
 }
 
 ChannelArguments::ChannelArguments(const ChannelArguments& other)
@@ -143,7 +143,7 @@
 }
 
 void ChannelArguments::SetResourceQuota(
-    const grpc::ResourceQuota& resource_quota) {
+    const grpc_impl::ResourceQuota& resource_quota) {
   SetPointerWithVtable(GRPC_ARG_RESOURCE_QUOTA,
                        resource_quota.c_resource_quota(),
                        grpc_resource_quota_arg_vtable());
@@ -215,4 +215,4 @@
   }
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/common/completion_queue_cc.cc b/src/cpp/common/completion_queue_cc.cc
index 4bb3bcb..43c2eee 100644
--- a/src/cpp/common/completion_queue_cc.cc
+++ b/src/cpp/common/completion_queue_cc.cc
@@ -24,9 +24,9 @@
 #include <grpcpp/impl/grpc_library.h>
 #include <grpcpp/support/time.h>
 
-namespace grpc {
+namespace grpc_impl {
 
-static internal::GrpcLibraryInitializer g_gli_initializer;
+static ::grpc::internal::GrpcLibraryInitializer g_gli_initializer;
 
 // 'CompletionQueue' constructor can safely call GrpcLibraryCodegen(false) here
 // i.e not have GrpcLibraryCodegen call grpc_init(). This is because, to create
@@ -52,7 +52,8 @@
       case GRPC_QUEUE_SHUTDOWN:
         return SHUTDOWN;
       case GRPC_OP_COMPLETE:
-        auto core_cq_tag = static_cast<internal::CompletionQueueTag*>(ev.tag);
+        auto core_cq_tag =
+            static_cast<::grpc::internal::CompletionQueueTag*>(ev.tag);
         *ok = ev.success != 0;
         *tag = core_cq_tag;
         if (core_cq_tag->FinalizeResult(tag, ok)) {
@@ -79,7 +80,8 @@
   flushed_ = true;
   if (grpc_completion_queue_thread_local_cache_flush(cq_->cq_, &res_tag,
                                                      &res)) {
-    auto core_cq_tag = static_cast<internal::CompletionQueueTag*>(res_tag);
+    auto core_cq_tag =
+        static_cast<::grpc::internal::CompletionQueueTag*>(res_tag);
     *ok = res == 1;
     if (core_cq_tag->FinalizeResult(tag, ok)) {
       return true;
@@ -88,4 +90,4 @@
   return false;
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/common/resource_quota_cc.cc b/src/cpp/common/resource_quota_cc.cc
index 276e5f7..4fab297 100644
--- a/src/cpp/common/resource_quota_cc.cc
+++ b/src/cpp/common/resource_quota_cc.cc
@@ -19,7 +19,7 @@
 #include <grpc/grpc.h>
 #include <grpcpp/resource_quota.h>
 
-namespace grpc {
+namespace grpc_impl {
 
 ResourceQuota::ResourceQuota() : impl_(grpc_resource_quota_create(nullptr)) {}
 
@@ -37,4 +37,4 @@
   grpc_resource_quota_set_max_threads(impl_, new_max_threads);
   return *this;
 }
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/common/secure_channel_arguments.cc b/src/cpp/common/secure_channel_arguments.cc
index 2fb8ea4..e5d03cd 100644
--- a/src/cpp/common/secure_channel_arguments.cc
+++ b/src/cpp/common/secure_channel_arguments.cc
@@ -21,7 +21,7 @@
 #include <grpc/grpc_security.h>
 #include "src/core/lib/channel/channel_args.h"
 
-namespace grpc {
+namespace grpc_impl {
 
 void ChannelArguments::SetSslTargetNameOverride(const grpc::string& name) {
   SetString(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, name);
@@ -36,4 +36,4 @@
   return "";
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/common/version_cc.cc b/src/cpp/common/version_cc.cc
index 2c17f47..166c2d3 100644
--- a/src/cpp/common/version_cc.cc
+++ b/src/cpp/common/version_cc.cc
@@ -22,5 +22,5 @@
 #include <grpcpp/grpcpp.h>
 
 namespace grpc {
-grpc::string Version() { return "1.20.1"; }
+grpc::string Version() { return "1.21.0-dev"; }
 }  // namespace grpc
diff --git a/src/cpp/ext/proto_server_reflection_plugin.cc b/src/cpp/ext/proto_server_reflection_plugin.cc
index ee3ac3f..0ac4cb8 100644
--- a/src/cpp/ext/proto_server_reflection_plugin.cc
+++ b/src/cpp/ext/proto_server_reflection_plugin.cc
@@ -23,7 +23,7 @@
 
 #include "src/cpp/ext/proto_server_reflection.h"
 
-namespace grpc {
+namespace grpc_impl {
 namespace reflection {
 
 ProtoServerReflectionPlugin::ProtoServerReflectionPlugin()
@@ -79,4 +79,4 @@
 } static_proto_reflection_plugin_initializer;
 
 }  // namespace reflection
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/channelz/channelz_service.cc b/src/cpp/server/channelz/channelz_service.cc
index c44a9ac..0409991 100644
--- a/src/cpp/server/channelz/channelz_service.cc
+++ b/src/cpp/server/channelz/channelz_service.cc
@@ -24,6 +24,12 @@
 #include <grpc/support/alloc.h>
 
 namespace grpc {
+grpc::protobuf::util::Status ParseJson(const char* json_str,
+                                       grpc::protobuf::Message* message) {
+  grpc::protobuf::json::JsonParseOptions options;
+  options.case_insensitive_enum_parsing = true;
+  return grpc::protobuf::json::JsonStringToMessage(json_str, message, options);
+}
 
 Status ChannelzService::GetTopChannels(
     ServerContext* unused, const channelz::v1::GetTopChannelsRequest* request,
@@ -33,8 +39,7 @@
     return Status(StatusCode::INTERNAL,
                   "grpc_channelz_get_top_channels returned null");
   }
-  grpc::protobuf::util::Status s =
-      grpc::protobuf::json::JsonStringToMessage(json_str, response);
+  grpc::protobuf::util::Status s = ParseJson(json_str, response);
   gpr_free(json_str);
   if (!s.ok()) {
     return Status(StatusCode::INTERNAL, s.ToString());
@@ -50,8 +55,7 @@
     return Status(StatusCode::INTERNAL,
                   "grpc_channelz_get_servers returned null");
   }
-  grpc::protobuf::util::Status s =
-      grpc::protobuf::json::JsonStringToMessage(json_str, response);
+  grpc::protobuf::util::Status s = ParseJson(json_str, response);
   gpr_free(json_str);
   if (!s.ok()) {
     return Status(StatusCode::INTERNAL, s.ToString());
@@ -67,8 +71,7 @@
     return Status(StatusCode::INTERNAL,
                   "grpc_channelz_get_server returned null");
   }
-  grpc::protobuf::util::Status s =
-      grpc::protobuf::json::JsonStringToMessage(json_str, response);
+  grpc::protobuf::util::Status s = ParseJson(json_str, response);
   gpr_free(json_str);
   if (!s.ok()) {
     return Status(StatusCode::INTERNAL, s.ToString());
@@ -85,8 +88,7 @@
     return Status(StatusCode::INTERNAL,
                   "grpc_channelz_get_server_sockets returned null");
   }
-  grpc::protobuf::util::Status s =
-      grpc::protobuf::json::JsonStringToMessage(json_str, response);
+  grpc::protobuf::util::Status s = ParseJson(json_str, response);
   gpr_free(json_str);
   if (!s.ok()) {
     return Status(StatusCode::INTERNAL, s.ToString());
@@ -101,8 +103,7 @@
   if (json_str == nullptr) {
     return Status(StatusCode::NOT_FOUND, "No object found for that ChannelId");
   }
-  grpc::protobuf::util::Status s =
-      grpc::protobuf::json::JsonStringToMessage(json_str, response);
+  grpc::protobuf::util::Status s = ParseJson(json_str, response);
   gpr_free(json_str);
   if (!s.ok()) {
     return Status(StatusCode::INTERNAL, s.ToString());
@@ -118,8 +119,7 @@
     return Status(StatusCode::NOT_FOUND,
                   "No object found for that SubchannelId");
   }
-  grpc::protobuf::util::Status s =
-      grpc::protobuf::json::JsonStringToMessage(json_str, response);
+  grpc::protobuf::util::Status s = ParseJson(json_str, response);
   gpr_free(json_str);
   if (!s.ok()) {
     return Status(StatusCode::INTERNAL, s.ToString());
@@ -134,8 +134,7 @@
   if (json_str == nullptr) {
     return Status(StatusCode::NOT_FOUND, "No object found for that SocketId");
   }
-  grpc::protobuf::util::Status s =
-      grpc::protobuf::json::JsonStringToMessage(json_str, response);
+  grpc::protobuf::util::Status s = ParseJson(json_str, response);
   gpr_free(json_str);
   if (!s.ok()) {
     return Status(StatusCode::INTERNAL, s.ToString());
diff --git a/src/cpp/server/dynamic_thread_pool.cc b/src/cpp/server/dynamic_thread_pool.cc
index ef99d64..c8bdbde 100644
--- a/src/cpp/server/dynamic_thread_pool.cc
+++ b/src/cpp/server/dynamic_thread_pool.cc
@@ -21,6 +21,7 @@
 #include <mutex>
 
 #include <grpc/support/log.h>
+#include <grpcpp/impl/codegen/sync.h>
 
 #include "src/core/lib/gprpp/thd.h"
 
@@ -40,27 +41,27 @@
 void DynamicThreadPool::DynamicThread::ThreadFunc() {
   pool_->ThreadFunc();
   // Now that we have killed ourselves, we should reduce the thread count
-  std::unique_lock<std::mutex> lock(pool_->mu_);
+  grpc_core::MutexLock lock(&pool_->mu_);
   pool_->nthreads_--;
   // Move ourselves to dead list
   pool_->dead_threads_.push_back(this);
 
   if ((pool_->shutdown_) && (pool_->nthreads_ == 0)) {
-    pool_->shutdown_cv_.notify_one();
+    pool_->shutdown_cv_.Signal();
   }
 }
 
 void DynamicThreadPool::ThreadFunc() {
   for (;;) {
     // Wait until work is available or we are shutting down.
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc_core::ReleasableMutexLock lock(&mu_);
     if (!shutdown_ && callbacks_.empty()) {
       // If there are too many threads waiting, then quit this thread
       if (threads_waiting_ >= reserve_threads_) {
         break;
       }
       threads_waiting_++;
-      cv_.wait(lock);
+      cv_.Wait(&mu_);
       threads_waiting_--;
     }
     // Drain callbacks before considering shutdown to ensure all work
@@ -68,7 +69,7 @@
     if (!callbacks_.empty()) {
       auto cb = callbacks_.front();
       callbacks_.pop();
-      lock.unlock();
+      lock.Unlock();
       cb();
     } else if (shutdown_) {
       break;
@@ -82,7 +83,7 @@
       nthreads_(0),
       threads_waiting_(0) {
   for (int i = 0; i < reserve_threads_; i++) {
-    std::lock_guard<std::mutex> lock(mu_);
+    grpc_core::MutexLock lock(&mu_);
     nthreads_++;
     new DynamicThread(this);
   }
@@ -95,17 +96,17 @@
 }
 
 DynamicThreadPool::~DynamicThreadPool() {
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   shutdown_ = true;
-  cv_.notify_all();
+  cv_.Broadcast();
   while (nthreads_ != 0) {
-    shutdown_cv_.wait(lock);
+    shutdown_cv_.Wait(&mu_);
   }
   ReapThreads(&dead_threads_);
 }
 
 void DynamicThreadPool::Add(const std::function<void()>& callback) {
-  std::lock_guard<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   // Add works to the callbacks list
   callbacks_.push(callback);
   // Increase pool size or notify as needed
@@ -114,7 +115,7 @@
     nthreads_++;
     new DynamicThread(this);
   } else {
-    cv_.notify_one();
+    cv_.Signal();
   }
   // Also use this chance to harvest dead threads
   if (!dead_threads_.empty()) {
diff --git a/src/cpp/server/dynamic_thread_pool.h b/src/cpp/server/dynamic_thread_pool.h
index 5df8cf2..4ae0257 100644
--- a/src/cpp/server/dynamic_thread_pool.h
+++ b/src/cpp/server/dynamic_thread_pool.h
@@ -27,6 +27,7 @@
 
 #include <grpcpp/support/config.h>
 
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/gprpp/thd.h"
 #include "src/cpp/server/thread_pool_interface.h"
 
@@ -50,9 +51,9 @@
     grpc_core::Thread thd_;
     void ThreadFunc();
   };
-  std::mutex mu_;
-  std::condition_variable cv_;
-  std::condition_variable shutdown_cv_;
+  grpc_core::Mutex mu_;
+  grpc_core::CondVar cv_;
+  grpc_core::CondVar shutdown_cv_;
   bool shutdown_;
   std::queue<std::function<void()>> callbacks_;
   int reserve_threads_;
diff --git a/src/cpp/server/health/default_health_check_service.cc b/src/cpp/server/health/default_health_check_service.cc
index 44aebd2..01bc51a 100644
--- a/src/cpp/server/health/default_health_check_service.cc
+++ b/src/cpp/server/health/default_health_check_service.cc
@@ -41,7 +41,7 @@
 
 void DefaultHealthCheckService::SetServingStatus(
     const grpc::string& service_name, bool serving) {
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   if (shutdown_) {
     // Set to NOT_SERVING in case service_name is not in the map.
     serving = false;
@@ -51,7 +51,7 @@
 
 void DefaultHealthCheckService::SetServingStatus(bool serving) {
   const ServingStatus status = serving ? SERVING : NOT_SERVING;
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   if (shutdown_) {
     return;
   }
@@ -62,7 +62,7 @@
 }
 
 void DefaultHealthCheckService::Shutdown() {
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   if (shutdown_) {
     return;
   }
@@ -76,7 +76,7 @@
 DefaultHealthCheckService::ServingStatus
 DefaultHealthCheckService::GetServingStatus(
     const grpc::string& service_name) const {
-  std::lock_guard<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   auto it = services_map_.find(service_name);
   if (it == services_map_.end()) {
     return NOT_FOUND;
@@ -88,7 +88,7 @@
 void DefaultHealthCheckService::RegisterCallHandler(
     const grpc::string& service_name,
     std::shared_ptr<HealthCheckServiceImpl::CallHandler> handler) {
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   ServiceData& service_data = services_map_[service_name];
   service_data.AddCallHandler(handler /* copies ref */);
   HealthCheckServiceImpl::CallHandler* h = handler.get();
@@ -98,7 +98,7 @@
 void DefaultHealthCheckService::UnregisterCallHandler(
     const grpc::string& service_name,
     const std::shared_ptr<HealthCheckServiceImpl::CallHandler>& handler) {
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   auto it = services_map_.find(service_name);
   if (it == services_map_.end()) return;
   ServiceData& service_data = it->second;
@@ -166,7 +166,7 @@
   // We will reach here after the server starts shutting down.
   shutdown_ = true;
   {
-    std::unique_lock<std::mutex> lock(cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&cq_shutdown_mu_);
     cq_->Shutdown();
   }
   thread_->Join();
@@ -266,7 +266,7 @@
       std::make_shared<CheckCallHandler>(cq, database, service);
   CheckCallHandler* handler = static_cast<CheckCallHandler*>(self.get());
   {
-    std::unique_lock<std::mutex> lock(service->cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&service->cq_shutdown_mu_);
     if (service->shutdown_) return;
     // Request a Check() call.
     handler->next_ =
@@ -311,7 +311,7 @@
   }
   // Send response.
   {
-    std::unique_lock<std::mutex> lock(service_->cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&service_->cq_shutdown_mu_);
     if (!service_->shutdown_) {
       next_ =
           CallableTag(std::bind(&CheckCallHandler::OnFinishDone, this,
@@ -347,7 +347,7 @@
       std::make_shared<WatchCallHandler>(cq, database, service);
   WatchCallHandler* handler = static_cast<WatchCallHandler*>(self.get());
   {
-    std::unique_lock<std::mutex> lock(service->cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&service->cq_shutdown_mu_);
     if (service->shutdown_) return;
     // Request AsyncNotifyWhenDone().
     handler->on_done_notified_ =
@@ -402,7 +402,7 @@
 
 void DefaultHealthCheckService::HealthCheckServiceImpl::WatchCallHandler::
     SendHealth(std::shared_ptr<CallHandler> self, ServingStatus status) {
-  std::unique_lock<std::mutex> lock(send_mu_);
+  grpc_core::MutexLock lock(&send_mu_);
   // If there's already a send in flight, cache the new status, and
   // we'll start a new send for it when the one in flight completes.
   if (send_in_flight_) {
@@ -420,7 +420,7 @@
   ByteBuffer response;
   bool success = service_->EncodeResponse(status, &response);
   // Grab shutdown lock and send response.
-  std::unique_lock<std::mutex> cq_lock(service_->cq_shutdown_mu_);
+  grpc_core::MutexLock cq_lock(&service_->cq_shutdown_mu_);
   if (service_->shutdown_) {
     SendFinishLocked(std::move(self), Status::CANCELLED);
     return;
@@ -442,7 +442,7 @@
     SendFinish(std::move(self), Status::CANCELLED);
     return;
   }
-  std::unique_lock<std::mutex> lock(send_mu_);
+  grpc_core::MutexLock lock(&send_mu_);
   send_in_flight_ = false;
   // If we got a new status since we started the last send, start a
   // new send for it.
@@ -456,7 +456,7 @@
 void DefaultHealthCheckService::HealthCheckServiceImpl::WatchCallHandler::
     SendFinish(std::shared_ptr<CallHandler> self, const Status& status) {
   if (finish_called_) return;
-  std::unique_lock<std::mutex> cq_lock(service_->cq_shutdown_mu_);
+  grpc_core::MutexLock cq_lock(&service_->cq_shutdown_mu_);
   if (service_->shutdown_) return;
   SendFinishLocked(std::move(self), status);
 }
diff --git a/src/cpp/server/health/default_health_check_service.h b/src/cpp/server/health/default_health_check_service.h
index 9551cd2..4b926ef 100644
--- a/src/cpp/server/health/default_health_check_service.h
+++ b/src/cpp/server/health/default_health_check_service.h
@@ -31,6 +31,7 @@
 #include <grpcpp/impl/codegen/service_type.h>
 #include <grpcpp/support/byte_buffer.h>
 
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/gprpp/thd.h"
 
 namespace grpc {
@@ -197,7 +198,7 @@
       GenericServerAsyncWriter stream_;
       ServerContext ctx_;
 
-      std::mutex send_mu_;
+      grpc_core::Mutex send_mu_;
       bool send_in_flight_ = false;               // Guarded by mu_.
       ServingStatus pending_status_ = NOT_FOUND;  // Guarded by mu_.
 
@@ -226,7 +227,7 @@
 
     // To synchronize the operations related to shutdown state of cq_, so that
     // we don't enqueue new tags into cq_ after it is already shut down.
-    std::mutex cq_shutdown_mu_;
+    grpc_core::Mutex cq_shutdown_mu_;
     std::atomic_bool shutdown_{false};
     std::unique_ptr<::grpc_core::Thread> thread_;
   };
@@ -273,7 +274,7 @@
       const grpc::string& service_name,
       const std::shared_ptr<HealthCheckServiceImpl::CallHandler>& handler);
 
-  mutable std::mutex mu_;
+  mutable grpc_core::Mutex mu_;
   bool shutdown_ = false;                             // Guarded by mu_.
   std::map<grpc::string, ServiceData> services_map_;  // Guarded by mu_.
   std::unique_ptr<HealthCheckServiceImpl> impl_;
diff --git a/src/cpp/server/health/health_check_service.cc b/src/cpp/server/health/health_check_service.cc
index a0fa2d6..ca0b49a 100644
--- a/src/cpp/server/health/health_check_service.cc
+++ b/src/cpp/server/health/health_check_service.cc
@@ -16,9 +16,9 @@
  *
  */
 
-#include <grpcpp/health_check_service_interface.h>
+#include <grpcpp/health_check_service_interface_impl.h>
 
-namespace grpc {
+namespace grpc_impl {
 namespace {
 bool g_grpc_default_health_check_service_enabled = false;
 }  // namespace
@@ -31,4 +31,4 @@
   g_grpc_default_health_check_service_enabled = enable;
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/insecure_server_credentials.cc b/src/cpp/server/insecure_server_credentials.cc
index 7d749dd..e4623ec 100644
--- a/src/cpp/server/insecure_server_credentials.cc
+++ b/src/cpp/server/insecure_server_credentials.cc
@@ -21,7 +21,7 @@
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
 
-namespace grpc {
+namespace grpc_impl {
 namespace {
 class InsecureServerCredentialsImpl final : public ServerCredentials {
  public:
@@ -29,7 +29,7 @@
     return grpc_server_add_insecure_http2_port(server, addr.c_str());
   }
   void SetAuthMetadataProcessor(
-      const std::shared_ptr<AuthMetadataProcessor>& processor) override {
+      const std::shared_ptr<grpc::AuthMetadataProcessor>& processor) override {
     (void)processor;
     GPR_ASSERT(0);  // Should not be called on InsecureServerCredentials.
   }
@@ -41,4 +41,4 @@
       new InsecureServerCredentialsImpl());
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/load_reporter/load_reporter.cc b/src/cpp/server/load_reporter/load_reporter.cc
index 464063a..422ea62 100644
--- a/src/cpp/server/load_reporter/load_reporter.cc
+++ b/src/cpp/server/load_reporter/load_reporter.cc
@@ -239,7 +239,7 @@
 
 ::grpc::lb::v1::LoadBalancingFeedback
 LoadReporter::GenerateLoadBalancingFeedback() {
-  std::unique_lock<std::mutex> lock(feedback_mu_);
+  grpc_core::ReleasableMutexLock lock(&feedback_mu_);
   auto now = std::chrono::system_clock::now();
   // Discard records outside the window until there is only one record
   // outside the window, which is used as the base for difference.
@@ -277,7 +277,7 @@
   double cpu_limit = newest->cpu_limit - oldest->cpu_limit;
   std::chrono::duration<double> duration_seconds =
       newest->end_time - oldest->end_time;
-  lock.unlock();
+  lock.Unlock();
   ::grpc::lb::v1::LoadBalancingFeedback feedback;
   feedback.set_server_utilization(static_cast<float>(cpu_usage / cpu_limit));
   feedback.set_calls_per_second(
@@ -290,7 +290,7 @@
 ::google::protobuf::RepeatedPtrField<::grpc::lb::v1::Load>
 LoadReporter::GenerateLoads(const grpc::string& hostname,
                             const grpc::string& lb_id) {
-  std::lock_guard<std::mutex> lock(store_mu_);
+  grpc_core::MutexLock lock(&store_mu_);
   auto assigned_stores = load_data_store_.GetAssignedStores(hostname, lb_id);
   GPR_ASSERT(assigned_stores != nullptr);
   GPR_ASSERT(!assigned_stores->empty());
@@ -371,7 +371,7 @@
     // This will make the load balancing feedback generation a no-op.
     cpu_stats = {0, 0};
   }
-  std::unique_lock<std::mutex> lock(feedback_mu_);
+  grpc_core::MutexLock lock(&feedback_mu_);
   feedback_records_.emplace_back(std::chrono::system_clock::now(), rpcs, errors,
                                  cpu_stats.first, cpu_stats.second);
 }
@@ -379,7 +379,7 @@
 void LoadReporter::ReportStreamCreated(const grpc::string& hostname,
                                        const grpc::string& lb_id,
                                        const grpc::string& load_key) {
-  std::lock_guard<std::mutex> lock(store_mu_);
+  grpc_core::MutexLock lock(&store_mu_);
   load_data_store_.ReportStreamCreated(hostname, lb_id, load_key);
   gpr_log(GPR_INFO,
           "[LR %p] Report stream created (host: %s, LB ID: %s, load key: %s).",
@@ -388,7 +388,7 @@
 
 void LoadReporter::ReportStreamClosed(const grpc::string& hostname,
                                       const grpc::string& lb_id) {
-  std::lock_guard<std::mutex> lock(store_mu_);
+  grpc_core::MutexLock lock(&store_mu_);
   load_data_store_.ReportStreamClosed(hostname, lb_id);
   gpr_log(GPR_INFO, "[LR %p] Report stream closed (host: %s, LB ID: %s).", this,
           hostname.c_str(), lb_id.c_str());
@@ -407,7 +407,7 @@
       LoadRecordKey key(client_ip_and_token, user_id);
       LoadRecordValue value = LoadRecordValue(start_count);
       {
-        std::unique_lock<std::mutex> lock(store_mu_);
+        grpc_core::MutexLock lock(&store_mu_);
         load_data_store_.MergeRow(host, key, value);
       }
     }
@@ -459,7 +459,7 @@
       LoadRecordValue value = LoadRecordValue(
           0, ok_count, error_count, bytes_sent, bytes_received, latency_ms);
       {
-        std::unique_lock<std::mutex> lock(store_mu_);
+        grpc_core::MutexLock lock(&store_mu_);
         load_data_store_.MergeRow(host, key, value);
       }
     }
@@ -486,7 +486,7 @@
       LoadRecordValue value = LoadRecordValue(
           metric_name, static_cast<uint64_t>(num_calls), total_metric_value);
       {
-        std::unique_lock<std::mutex> lock(store_mu_);
+        grpc_core::MutexLock lock(&store_mu_);
         load_data_store_.MergeRow(host, key, value);
       }
     }
diff --git a/src/cpp/server/load_reporter/load_reporter.h b/src/cpp/server/load_reporter/load_reporter.h
index b2254d5..766e02a 100644
--- a/src/cpp/server/load_reporter/load_reporter.h
+++ b/src/cpp/server/load_reporter/load_reporter.h
@@ -29,6 +29,7 @@
 #include <grpc/support/log.h>
 #include <grpcpp/impl/codegen/config.h>
 
+#include "src/core/lib/gprpp/sync.h"
 #include "src/cpp/server/load_reporter/load_data_store.h"
 #include "src/proto/grpc/lb/v1/load_reporter.grpc.pb.h"
 
@@ -212,11 +213,11 @@
 
   std::atomic<int64_t> next_lb_id_{0};
   const std::chrono::seconds feedback_sample_window_seconds_;
-  std::mutex feedback_mu_;
+  grpc_core::Mutex feedback_mu_;
   std::deque<LoadBalancingFeedbackRecord> feedback_records_;
   // TODO(juanlishen): Lock in finer grain. Locking the whole store may be
   // too expensive.
-  std::mutex store_mu_;
+  grpc_core::Mutex store_mu_;
   LoadDataStore load_data_store_;
   std::unique_ptr<CensusViewProvider> census_view_provider_;
   std::unique_ptr<CpuStatsProvider> cpu_stats_provider_;
diff --git a/src/cpp/server/load_reporter/load_reporter_async_service_impl.cc b/src/cpp/server/load_reporter/load_reporter_async_service_impl.cc
index 859ad99..9eaab5d 100644
--- a/src/cpp/server/load_reporter/load_reporter_async_service_impl.cc
+++ b/src/cpp/server/load_reporter/load_reporter_async_service_impl.cc
@@ -48,7 +48,7 @@
   // We will reach here after the server starts shutting down.
   shutdown_ = true;
   {
-    std::unique_lock<std::mutex> lock(cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&cq_shutdown_mu_);
     cq_->Shutdown();
   }
   if (next_fetch_and_sample_alarm_ != nullptr)
@@ -62,7 +62,7 @@
                    gpr_time_from_millis(kFetchAndSampleIntervalSeconds * 1000,
                                         GPR_TIMESPAN));
   {
-    std::unique_lock<std::mutex> lock(cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&cq_shutdown_mu_);
     if (shutdown_) return;
     // TODO(juanlishen): Improve the Alarm implementation to reuse a single
     // instance for multiple events.
@@ -119,7 +119,7 @@
       std::make_shared<ReportLoadHandler>(cq, service, load_reporter);
   ReportLoadHandler* p = handler.get();
   {
-    std::unique_lock<std::mutex> lock(service->cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&service->cq_shutdown_mu_);
     if (service->shutdown_) return;
     p->on_done_notified_ =
         CallableTag(std::bind(&ReportLoadHandler::OnDoneNotified, p,
@@ -164,9 +164,9 @@
   // instance will deallocate itself when it's done.
   CreateAndStart(cq_, service_, load_reporter_);
   {
-    std::unique_lock<std::mutex> lock(service_->cq_shutdown_mu_);
+    grpc_core::ReleasableMutexLock lock(&service_->cq_shutdown_mu_);
     if (service_->shutdown_) {
-      lock.release()->unlock();
+      lock.Unlock();
       Shutdown(std::move(self), "OnRequestDelivered");
       return;
     }
@@ -222,9 +222,9 @@
       SendReport(self, true /* ok */);
       // Expect this read to fail.
       {
-        std::unique_lock<std::mutex> lock(service_->cq_shutdown_mu_);
+        grpc_core::ReleasableMutexLock lock(&service_->cq_shutdown_mu_);
         if (service_->shutdown_) {
-          lock.release()->unlock();
+          lock.Unlock();
           Shutdown(std::move(self), "OnReadDone");
           return;
         }
@@ -254,9 +254,9 @@
       gpr_now(GPR_CLOCK_MONOTONIC),
       gpr_time_from_millis(load_report_interval_ms_, GPR_TIMESPAN));
   {
-    std::unique_lock<std::mutex> lock(service_->cq_shutdown_mu_);
+    grpc_core::ReleasableMutexLock lock(&service_->cq_shutdown_mu_);
     if (service_->shutdown_) {
-      lock.release()->unlock();
+      lock.Unlock();
       Shutdown(std::move(self), "ScheduleNextReport");
       return;
     }
@@ -294,9 +294,9 @@
     call_status_ = INITIAL_RESPONSE_SENT;
   }
   {
-    std::unique_lock<std::mutex> lock(service_->cq_shutdown_mu_);
+    grpc_core::ReleasableMutexLock lock(&service_->cq_shutdown_mu_);
     if (service_->shutdown_) {
-      lock.release()->unlock();
+      lock.Unlock();
       Shutdown(std::move(self), "SendReport");
       return;
     }
@@ -342,7 +342,7 @@
   // OnRequestDelivered() may be called after OnDoneNotified(), so we need to
   // try to Finish() every time we are in Shutdown().
   if (call_status_ >= DELIVERED && call_status_ < FINISH_CALLED) {
-    std::unique_lock<std::mutex> lock(service_->cq_shutdown_mu_);
+    grpc_core::MutexLock lock(&service_->cq_shutdown_mu_);
     if (!service_->shutdown_) {
       on_finish_done_ =
           CallableTag(std::bind(&ReportLoadHandler::OnFinishDone, this,
diff --git a/src/cpp/server/load_reporter/load_reporter_async_service_impl.h b/src/cpp/server/load_reporter/load_reporter_async_service_impl.h
index 6fc577f..3087cbf 100644
--- a/src/cpp/server/load_reporter/load_reporter_async_service_impl.h
+++ b/src/cpp/server/load_reporter/load_reporter_async_service_impl.h
@@ -25,6 +25,7 @@
 #include <grpcpp/alarm.h>
 #include <grpcpp/grpcpp.h>
 
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/gprpp/thd.h"
 #include "src/cpp/server/load_reporter/load_reporter.h"
 
@@ -181,7 +182,7 @@
   std::unique_ptr<ServerCompletionQueue> cq_;
   // To synchronize the operations related to shutdown state of cq_, so that we
   // don't enqueue new tags into cq_ after it is already shut down.
-  std::mutex cq_shutdown_mu_;
+  grpc_core::Mutex cq_shutdown_mu_;
   std::atomic_bool shutdown_{false};
   std::unique_ptr<::grpc_core::Thread> thread_;
   std::unique_ptr<LoadReporter> load_reporter_;
diff --git a/src/cpp/server/load_reporter/load_reporting_service_server_builder_option.cc b/src/cpp/server/load_reporter/load_reporting_service_server_builder_option.cc
index 81cf6ac..ab63beb 100644
--- a/src/cpp/server/load_reporter/load_reporting_service_server_builder_option.cc
+++ b/src/cpp/server/load_reporter/load_reporting_service_server_builder_option.cc
@@ -22,7 +22,7 @@
 
 #include "src/cpp/server/load_reporter/load_reporting_service_server_builder_plugin.h"
 
-namespace grpc {
+namespace grpc_impl {
 namespace load_reporter {
 namespace experimental {
 
@@ -33,9 +33,10 @@
 
 void LoadReportingServiceServerBuilderOption::UpdatePlugins(
     std::vector<std::unique_ptr<::grpc::ServerBuilderPlugin>>* plugins) {
-  plugins->emplace_back(new LoadReportingServiceServerBuilderPlugin());
+  plugins->emplace_back(
+      new grpc::load_reporter::LoadReportingServiceServerBuilderPlugin());
 }
 
 }  // namespace experimental
 }  // namespace load_reporter
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/load_reporter/load_reporting_service_server_builder_plugin.h b/src/cpp/server/load_reporter/load_reporting_service_server_builder_plugin.h
index 1f09859..c80802b 100644
--- a/src/cpp/server/load_reporter/load_reporting_service_server_builder_plugin.h
+++ b/src/cpp/server/load_reporter/load_reporting_service_server_builder_plugin.h
@@ -36,13 +36,13 @@
   grpc::string name() override { return "load_reporting_service"; }
 
   // Creates a load reporting service.
-  void UpdateServerBuilder(grpc::ServerBuilder* builder) override;
+  void UpdateServerBuilder(ServerBuilder* builder) override;
 
   // Registers the load reporter service.
-  void InitServer(grpc::ServerInitializer* si) override;
+  void InitServer(grpc_impl::ServerInitializer* si) override;
 
   // Starts the load reporter service.
-  void Finish(grpc::ServerInitializer* si) override;
+  void Finish(grpc_impl::ServerInitializer* si) override;
 
   void ChangeArguments(const grpc::string& name, void* value) override {}
   void UpdateChannelArguments(grpc::ChannelArguments* args) override {}
diff --git a/src/cpp/server/load_reporter/util.cc b/src/cpp/server/load_reporter/util.cc
index 89bdf57..b69705a 100644
--- a/src/cpp/server/load_reporter/util.cc
+++ b/src/cpp/server/load_reporter/util.cc
@@ -24,7 +24,7 @@
 
 #include <grpc/support/log.h>
 
-namespace grpc {
+namespace grpc_impl {
 namespace load_reporter {
 namespace experimental {
 
@@ -44,4 +44,4 @@
 
 }  // namespace experimental
 }  // namespace load_reporter
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/secure_server_credentials.cc b/src/cpp/server/secure_server_credentials.cc
index 453e76e..93dc10f 100644
--- a/src/cpp/server/secure_server_credentials.cc
+++ b/src/cpp/server/secure_server_credentials.cc
@@ -93,21 +93,25 @@
      status.error_message().c_str());
 }
 
+}  // namespace grpc
+
+namespace grpc_impl {
+
 int SecureServerCredentials::AddPortToServer(const grpc::string& addr,
                                              grpc_server* server) {
   return grpc_server_add_secure_http2_port(server, addr.c_str(), creds_);
 }
 
 void SecureServerCredentials::SetAuthMetadataProcessor(
-    const std::shared_ptr<AuthMetadataProcessor>& processor) {
-  auto* wrapper = new AuthMetadataProcessorAyncWrapper(processor);
+    const std::shared_ptr<grpc::AuthMetadataProcessor>& processor) {
+  auto* wrapper = new grpc::AuthMetadataProcessorAyncWrapper(processor);
   grpc_server_credentials_set_auth_metadata_processor(
-      creds_, {AuthMetadataProcessorAyncWrapper::Process,
-               AuthMetadataProcessorAyncWrapper::Destroy, wrapper});
+      creds_, {grpc::AuthMetadataProcessorAyncWrapper::Process,
+               grpc::AuthMetadataProcessorAyncWrapper::Destroy, wrapper});
 }
 
 std::shared_ptr<ServerCredentials> SslServerCredentials(
-    const SslServerCredentialsOptions& options) {
+    const grpc::SslServerCredentialsOptions& options) {
   std::vector<grpc_ssl_pem_key_cert_pair> pem_key_cert_pairs;
   for (auto key_cert_pair = options.pem_key_cert_pairs.begin();
        key_cert_pair != options.pem_key_cert_pairs.end(); key_cert_pair++) {
@@ -147,4 +151,4 @@
 }
 
 }  // namespace experimental
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/secure_server_credentials.h b/src/cpp/server/secure_server_credentials.h
index 8a81af2..24b133c 100644
--- a/src/cpp/server/secure_server_credentials.h
+++ b/src/cpp/server/secure_server_credentials.h
@@ -27,8 +27,15 @@
 
 #include "src/cpp/server/thread_pool_interface.h"
 
+namespace grpc_impl {
+
+class SecureServerCredentials;
+}  // namespace grpc_impl
+
 namespace grpc {
 
+typedef ::grpc_impl::SecureServerCredentials SecureServerCredentials;
+
 class AuthMetadataProcessorAyncWrapper final {
  public:
   static void Destroy(void* wrapper);
@@ -49,6 +56,10 @@
   std::shared_ptr<AuthMetadataProcessor> processor_;
 };
 
+}  // namespace grpc
+
+namespace grpc_impl {
+
 class SecureServerCredentials final : public ServerCredentials {
  public:
   explicit SecureServerCredentials(grpc_server_credentials* creds)
@@ -60,13 +71,13 @@
   int AddPortToServer(const grpc::string& addr, grpc_server* server) override;
 
   void SetAuthMetadataProcessor(
-      const std::shared_ptr<AuthMetadataProcessor>& processor) override;
+      const std::shared_ptr<grpc::AuthMetadataProcessor>& processor) override;
 
  private:
   grpc_server_credentials* creds_;
-  std::unique_ptr<AuthMetadataProcessorAyncWrapper> processor_;
+  std::unique_ptr<grpc::AuthMetadataProcessorAyncWrapper> processor_;
 };
 
-}  // namespace grpc
+}  // namespace grpc_impl
 
 #endif  // GRPC_INTERNAL_CPP_SERVER_SECURE_SERVER_CREDENTIALS_H
diff --git a/src/cpp/server/server_builder.cc b/src/cpp/server/server_builder.cc
index cd0e516..af76ffb 100644
--- a/src/cpp/server/server_builder.cc
+++ b/src/cpp/server/server_builder.cc
@@ -29,15 +29,15 @@
 #include "src/core/lib/gpr/useful.h"
 #include "src/cpp/server/thread_pool_interface.h"
 
-namespace grpc {
+namespace grpc_impl {
 
-static std::vector<std::unique_ptr<ServerBuilderPlugin> (*)()>*
+static std::vector<std::unique_ptr<grpc::ServerBuilderPlugin> (*)()>*
     g_plugin_factory_list;
 static gpr_once once_init_plugin_list = GPR_ONCE_INIT;
 
 static void do_plugin_list_init(void) {
   g_plugin_factory_list =
-      new std::vector<std::unique_ptr<ServerBuilderPlugin> (*)()>();
+      new std::vector<std::unique_ptr<grpc::ServerBuilderPlugin> (*)()>();
 }
 
 ServerBuilder::ServerBuilder()
@@ -67,29 +67,29 @@
   }
 }
 
-std::unique_ptr<ServerCompletionQueue> ServerBuilder::AddCompletionQueue(
+std::unique_ptr<grpc::ServerCompletionQueue> ServerBuilder::AddCompletionQueue(
     bool is_frequently_polled) {
-  ServerCompletionQueue* cq = new ServerCompletionQueue(
+  grpc::ServerCompletionQueue* cq = new grpc::ServerCompletionQueue(
       GRPC_CQ_NEXT,
       is_frequently_polled ? GRPC_CQ_DEFAULT_POLLING : GRPC_CQ_NON_LISTENING,
       nullptr);
   cqs_.push_back(cq);
-  return std::unique_ptr<ServerCompletionQueue>(cq);
+  return std::unique_ptr<grpc::ServerCompletionQueue>(cq);
 }
 
-ServerBuilder& ServerBuilder::RegisterService(Service* service) {
+ServerBuilder& ServerBuilder::RegisterService(grpc::Service* service) {
   services_.emplace_back(new NamedService(service));
   return *this;
 }
 
 ServerBuilder& ServerBuilder::RegisterService(const grpc::string& addr,
-                                              Service* service) {
+                                              grpc::Service* service) {
   services_.emplace_back(new NamedService(addr, service));
   return *this;
 }
 
 ServerBuilder& ServerBuilder::RegisterAsyncGenericService(
-    AsyncGenericService* service) {
+    grpc::AsyncGenericService* service) {
   if (generic_service_ || callback_generic_service_) {
     gpr_log(GPR_ERROR,
             "Adding multiple generic services is unsupported for now. "
@@ -102,7 +102,7 @@
 }
 
 ServerBuilder& ServerBuilder::experimental_type::RegisterCallbackGenericService(
-    experimental::CallbackGenericService* service) {
+    grpc::experimental::CallbackGenericService* service) {
   if (builder_->generic_service_ || builder_->callback_generic_service_) {
     gpr_log(GPR_ERROR,
             "Adding multiple generic services is unsupported for now. "
@@ -115,7 +115,7 @@
 }
 
 ServerBuilder& ServerBuilder::SetOption(
-    std::unique_ptr<ServerBuilderOption> option) {
+    std::unique_ptr<grpc::ServerBuilderOption> option) {
   options_.push_back(std::move(option));
   return *this;
 }
@@ -164,7 +164,7 @@
 }
 
 ServerBuilder& ServerBuilder::SetResourceQuota(
-    const grpc::ResourceQuota& resource_quota) {
+    const grpc_impl::ResourceQuota& resource_quota) {
   if (resource_quota_ != nullptr) {
     grpc_resource_quota_unref(resource_quota_);
   }
@@ -174,8 +174,8 @@
 }
 
 ServerBuilder& ServerBuilder::AddListeningPort(
-    const grpc::string& addr_uri, std::shared_ptr<ServerCredentials> creds,
-    int* selected_port) {
+    const grpc::string& addr_uri,
+    std::shared_ptr<grpc::ServerCredentials> creds, int* selected_port) {
   const grpc::string uri_scheme = "dns:";
   grpc::string addr = addr_uri;
   if (addr_uri.compare(0, uri_scheme.size(), uri_scheme) == 0) {
@@ -188,8 +188,8 @@
   return *this;
 }
 
-std::unique_ptr<Server> ServerBuilder::BuildAndStart() {
-  ChannelArguments args;
+std::unique_ptr<grpc::Server> ServerBuilder::BuildAndStart() {
+  grpc::ChannelArguments args;
   for (auto option = options_.begin(); option != options_.end(); ++option) {
     (*option)->UpdateArguments(&args);
     (*option)->UpdatePlugins(&plugins_);
@@ -251,9 +251,10 @@
   // This is different from the completion queues added to the server via
   // ServerBuilder's AddCompletionQueue() method (those completion queues
   // are in 'cqs_' member variable of ServerBuilder object)
-  std::shared_ptr<std::vector<std::unique_ptr<ServerCompletionQueue>>>
-      sync_server_cqs(std::make_shared<
-                      std::vector<std::unique_ptr<ServerCompletionQueue>>>());
+  std::shared_ptr<std::vector<std::unique_ptr<grpc::ServerCompletionQueue>>>
+      sync_server_cqs(
+          std::make_shared<
+              std::vector<std::unique_ptr<grpc::ServerCompletionQueue>>>());
 
   bool has_frequently_polled_cqs = false;
   for (auto it = cqs_.begin(); it != cqs_.end(); ++it) {
@@ -282,7 +283,7 @@
     // Create completion queues to listen to incoming rpc requests
     for (int i = 0; i < sync_server_settings_.num_cqs; i++) {
       sync_server_cqs->emplace_back(
-          new ServerCompletionQueue(GRPC_CQ_NEXT, polling_type, nullptr));
+          new grpc::ServerCompletionQueue(GRPC_CQ_NEXT, polling_type, nullptr));
     }
   }
 
@@ -303,13 +304,13 @@
     gpr_log(GPR_INFO, "Callback server.");
   }
 
-  std::unique_ptr<Server> server(new Server(
+  std::unique_ptr<grpc::Server> server(new grpc::Server(
       max_receive_message_size_, &args, sync_server_cqs,
       sync_server_settings_.min_pollers, sync_server_settings_.max_pollers,
       sync_server_settings_.cq_timeout_msec, resource_quota_,
       std::move(interceptor_creators_)));
 
-  ServerInitializer* initializer = server->initializer();
+  grpc_impl::ServerInitializer* initializer = server->initializer();
 
   // Register all the completion queues with the server. i.e
   //  1. sync_server_cqs: internal completion queues created IF this is a sync
@@ -393,7 +394,7 @@
 }
 
 void ServerBuilder::InternalAddPluginFactory(
-    std::unique_ptr<ServerBuilderPlugin> (*CreatePlugin)()) {
+    std::unique_ptr<grpc::ServerBuilderPlugin> (*CreatePlugin)()) {
   gpr_once_init(&once_init_plugin_list, do_plugin_list_init);
   (*g_plugin_factory_list).push_back(CreatePlugin);
 }
@@ -408,4 +409,4 @@
   }
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/server_cc.cc b/src/cpp/server/server_cc.cc
index 26e84f1..9c2b1ee 100644
--- a/src/cpp/server/server_cc.cc
+++ b/src/cpp/server/server_cc.cc
@@ -106,1029 +106,6 @@
 
 }  // namespace
 
-/// Use private inheritance rather than composition only to establish order
-/// of construction, since the public base class should be constructed after the
-/// elements belonging to the private base class are constructed. This is not
-/// possible using true composition.
-class Server::UnimplementedAsyncRequest final
-    : private UnimplementedAsyncRequestContext,
-      public GenericAsyncRequest {
- public:
-  UnimplementedAsyncRequest(Server* server, ServerCompletionQueue* cq)
-      : GenericAsyncRequest(server, &server_context_, &generic_stream_, cq, cq,
-                            nullptr, false),
-        server_(server),
-        cq_(cq) {}
-
-  bool FinalizeResult(void** tag, bool* status) override;
-
-  ServerContext* context() { return &server_context_; }
-  GenericServerAsyncReaderWriter* stream() { return &generic_stream_; }
-
- private:
-  Server* const server_;
-  ServerCompletionQueue* const cq_;
-};
-
-/// UnimplementedAsyncResponse should not post user-visible completions to the
-/// C++ completion queue, but is generated as a CQ event by the core
-class Server::UnimplementedAsyncResponse final
-    : public internal::CallOpSet<internal::CallOpSendInitialMetadata,
-                                 internal::CallOpServerSendStatus> {
- public:
-  UnimplementedAsyncResponse(UnimplementedAsyncRequest* request);
-  ~UnimplementedAsyncResponse() { delete request_; }
-
-  bool FinalizeResult(void** tag, bool* status) override {
-    if (internal::CallOpSet<
-            internal::CallOpSendInitialMetadata,
-            internal::CallOpServerSendStatus>::FinalizeResult(tag, status)) {
-      delete this;
-    } else {
-      // The tag was swallowed due to interception. We will see it again.
-    }
-    return false;
-  }
-
- private:
-  UnimplementedAsyncRequest* const request_;
-};
-
-class Server::SyncRequest final : public internal::CompletionQueueTag {
- public:
-  SyncRequest(internal::RpcServiceMethod* method, void* method_tag)
-      : method_(method),
-        method_tag_(method_tag),
-        in_flight_(false),
-        has_request_payload_(
-            method->method_type() == internal::RpcMethod::NORMAL_RPC ||
-            method->method_type() == internal::RpcMethod::SERVER_STREAMING),
-        call_details_(nullptr),
-        cq_(nullptr) {
-    grpc_metadata_array_init(&request_metadata_);
-  }
-
-  ~SyncRequest() {
-    if (call_details_) {
-      delete call_details_;
-    }
-    grpc_metadata_array_destroy(&request_metadata_);
-  }
-
-  void SetupRequest() { cq_ = grpc_completion_queue_create_for_pluck(nullptr); }
-
-  void TeardownRequest() {
-    grpc_completion_queue_destroy(cq_);
-    cq_ = nullptr;
-  }
-
-  void Request(grpc_server* server, grpc_completion_queue* notify_cq) {
-    GPR_ASSERT(cq_ && !in_flight_);
-    in_flight_ = true;
-    if (method_tag_) {
-      if (grpc_server_request_registered_call(
-              server, method_tag_, &call_, &deadline_, &request_metadata_,
-              has_request_payload_ ? &request_payload_ : nullptr, cq_,
-              notify_cq, this) != GRPC_CALL_OK) {
-        TeardownRequest();
-        return;
-      }
-    } else {
-      if (!call_details_) {
-        call_details_ = new grpc_call_details;
-        grpc_call_details_init(call_details_);
-      }
-      if (grpc_server_request_call(server, &call_, call_details_,
-                                   &request_metadata_, cq_, notify_cq,
-                                   this) != GRPC_CALL_OK) {
-        TeardownRequest();
-        return;
-      }
-    }
-  }
-
-  void PostShutdownCleanup() {
-    if (call_) {
-      grpc_call_unref(call_);
-      call_ = nullptr;
-    }
-    if (cq_) {
-      grpc_completion_queue_destroy(cq_);
-      cq_ = nullptr;
-    }
-  }
-
-  bool FinalizeResult(void** tag, bool* status) override {
-    if (!*status) {
-      grpc_completion_queue_destroy(cq_);
-      cq_ = nullptr;
-    }
-    if (call_details_) {
-      deadline_ = call_details_->deadline;
-      grpc_call_details_destroy(call_details_);
-      grpc_call_details_init(call_details_);
-    }
-    return true;
-  }
-
-  // The CallData class represents a call that is "active" as opposed
-  // to just being requested. It wraps and takes ownership of the cq from
-  // the call request
-  class CallData final {
-   public:
-    explicit CallData(Server* server, SyncRequest* mrd)
-        : cq_(mrd->cq_),
-          ctx_(mrd->deadline_, &mrd->request_metadata_),
-          has_request_payload_(mrd->has_request_payload_),
-          request_payload_(has_request_payload_ ? mrd->request_payload_
-                                                : nullptr),
-          request_(nullptr),
-          method_(mrd->method_),
-          call_(
-              mrd->call_, server, &cq_, server->max_receive_message_size(),
-              ctx_.set_server_rpc_info(method_->name(), method_->method_type(),
-                                       server->interceptor_creators_)),
-          server_(server),
-          global_callbacks_(nullptr),
-          resources_(false) {
-      ctx_.set_call(mrd->call_);
-      ctx_.cq_ = &cq_;
-      GPR_ASSERT(mrd->in_flight_);
-      mrd->in_flight_ = false;
-      mrd->request_metadata_.count = 0;
-    }
-
-    ~CallData() {
-      if (has_request_payload_ && request_payload_) {
-        grpc_byte_buffer_destroy(request_payload_);
-      }
-    }
-
-    void Run(const std::shared_ptr<GlobalCallbacks>& global_callbacks,
-             bool resources) {
-      global_callbacks_ = global_callbacks;
-      resources_ = resources;
-
-      interceptor_methods_.SetCall(&call_);
-      interceptor_methods_.SetReverse();
-      // Set interception point for RECV INITIAL METADATA
-      interceptor_methods_.AddInterceptionHookPoint(
-          experimental::InterceptionHookPoints::POST_RECV_INITIAL_METADATA);
-      interceptor_methods_.SetRecvInitialMetadata(&ctx_.client_metadata_);
-
-      if (has_request_payload_) {
-        // Set interception point for RECV MESSAGE
-        auto* handler = resources_ ? method_->handler()
-                                   : server_->resource_exhausted_handler_.get();
-        request_ = handler->Deserialize(call_.call(), request_payload_,
-                                        &request_status_);
-
-        request_payload_ = nullptr;
-        interceptor_methods_.AddInterceptionHookPoint(
-            experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
-        interceptor_methods_.SetRecvMessage(request_, nullptr);
-      }
-
-      if (interceptor_methods_.RunInterceptors(
-              [this]() { ContinueRunAfterInterception(); })) {
-        ContinueRunAfterInterception();
-      } else {
-        // There were interceptors to be run, so ContinueRunAfterInterception
-        // will be run when interceptors are done.
-      }
-    }
-
-    void ContinueRunAfterInterception() {
-      {
-        ctx_.BeginCompletionOp(&call_, nullptr, nullptr);
-        global_callbacks_->PreSynchronousRequest(&ctx_);
-        auto* handler = resources_ ? method_->handler()
-                                   : server_->resource_exhausted_handler_.get();
-        handler->RunHandler(internal::MethodHandler::HandlerParameter(
-            &call_, &ctx_, request_, request_status_, nullptr));
-        request_ = nullptr;
-        global_callbacks_->PostSynchronousRequest(&ctx_);
-
-        cq_.Shutdown();
-
-        internal::CompletionQueueTag* op_tag = ctx_.GetCompletionOpTag();
-        cq_.TryPluck(op_tag, gpr_inf_future(GPR_CLOCK_REALTIME));
-
-        /* Ensure the cq_ is shutdown */
-        DummyTag ignored_tag;
-        GPR_ASSERT(cq_.Pluck(&ignored_tag) == false);
-      }
-      delete this;
-    }
-
-   private:
-    CompletionQueue cq_;
-    ServerContext ctx_;
-    const bool has_request_payload_;
-    grpc_byte_buffer* request_payload_;
-    void* request_;
-    Status request_status_;
-    internal::RpcServiceMethod* const method_;
-    internal::Call call_;
-    Server* server_;
-    std::shared_ptr<GlobalCallbacks> global_callbacks_;
-    bool resources_;
-    internal::InterceptorBatchMethodsImpl interceptor_methods_;
-  };
-
- private:
-  internal::RpcServiceMethod* const method_;
-  void* const method_tag_;
-  bool in_flight_;
-  const bool has_request_payload_;
-  grpc_call* call_;
-  grpc_call_details* call_details_;
-  gpr_timespec deadline_;
-  grpc_metadata_array request_metadata_;
-  grpc_byte_buffer* request_payload_;
-  grpc_completion_queue* cq_;
-};
-
-class Server::CallbackRequestBase : public internal::CompletionQueueTag {
- public:
-  virtual ~CallbackRequestBase() {}
-  virtual bool Request() = 0;
-};
-
-template <class ServerContextType>
-class Server::CallbackRequest final : public Server::CallbackRequestBase {
- public:
-  static_assert(std::is_base_of<ServerContext, ServerContextType>::value,
-                "ServerContextType must be derived from ServerContext");
-
-  // The constructor needs to know the server for this callback request and its
-  // index in the server's request count array to allow for proper dynamic
-  // requesting of incoming RPCs. For codegen services, the values of method and
-  // method_tag represent the defined characteristics of the method being
-  // requested. For generic services, method and method_tag are nullptr since
-  // these services don't have pre-defined methods or method registration tags.
-  CallbackRequest(Server* server, size_t method_idx,
-                  internal::RpcServiceMethod* method, void* method_tag)
-      : server_(server),
-        method_index_(method_idx),
-        method_(method),
-        method_tag_(method_tag),
-        has_request_payload_(
-            method_ != nullptr &&
-            (method->method_type() == internal::RpcMethod::NORMAL_RPC ||
-             method->method_type() == internal::RpcMethod::SERVER_STREAMING)),
-        cq_(server->CallbackCQ()),
-        tag_(this) {
-    server_->callback_reqs_outstanding_++;
-    Setup();
-  }
-
-  ~CallbackRequest() {
-    Clear();
-
-    // The counter of outstanding requests must be decremented
-    // under a lock in case it causes the server shutdown.
-    std::lock_guard<std::mutex> l(server_->callback_reqs_mu_);
-    if (--server_->callback_reqs_outstanding_ == 0) {
-      server_->callback_reqs_done_cv_.notify_one();
-    }
-  }
-
-  bool Request() override {
-    if (method_tag_) {
-      if (GRPC_CALL_OK !=
-          grpc_server_request_registered_call(
-              server_->c_server(), method_tag_, &call_, &deadline_,
-              &request_metadata_,
-              has_request_payload_ ? &request_payload_ : nullptr, cq_->cq(),
-              cq_->cq(), static_cast<void*>(&tag_))) {
-        return false;
-      }
-    } else {
-      if (!call_details_) {
-        call_details_ = new grpc_call_details;
-        grpc_call_details_init(call_details_);
-      }
-      if (grpc_server_request_call(server_->c_server(), &call_, call_details_,
-                                   &request_metadata_, cq_->cq(), cq_->cq(),
-                                   static_cast<void*>(&tag_)) != GRPC_CALL_OK) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  // Needs specialization to account for different processing of metadata
-  // in generic API
-  bool FinalizeResult(void** tag, bool* status) override;
-
- private:
-  // method_name needs to be specialized between named method and generic
-  const char* method_name() const;
-
-  class CallbackCallTag : public grpc_experimental_completion_queue_functor {
-   public:
-    CallbackCallTag(Server::CallbackRequest<ServerContextType>* req)
-        : req_(req) {
-      functor_run = &CallbackCallTag::StaticRun;
-    }
-
-    // force_run can not be performed on a tag if operations using this tag
-    // have been sent to PerformOpsOnCall. It is intended for error conditions
-    // that are detected before the operations are internally processed.
-    void force_run(bool ok) { Run(ok); }
-
-   private:
-    Server::CallbackRequest<ServerContextType>* req_;
-    internal::Call* call_;
-
-    static void StaticRun(grpc_experimental_completion_queue_functor* cb,
-                          int ok) {
-      static_cast<CallbackCallTag*>(cb)->Run(static_cast<bool>(ok));
-    }
-    void Run(bool ok) {
-      void* ignored = req_;
-      bool new_ok = ok;
-      GPR_ASSERT(!req_->FinalizeResult(&ignored, &new_ok));
-      GPR_ASSERT(ignored == req_);
-
-      int count =
-          static_cast<int>(gpr_atm_no_barrier_fetch_add(
-              &req_->server_
-                   ->callback_unmatched_reqs_count_[req_->method_index_],
-              -1)) -
-          1;
-      if (!ok) {
-        // The call has been shutdown.
-        // Delete its contents to free up the request.
-        delete req_;
-        return;
-      }
-
-      // If this was the last request in the list or it is below the soft
-      // minimum and there are spare requests available, set up a new one.
-      if (count == 0 || (count < SOFT_MINIMUM_SPARE_CALLBACK_REQS_PER_METHOD &&
-                         req_->server_->callback_reqs_outstanding_ <
-                             SOFT_MAXIMUM_CALLBACK_REQS_OUTSTANDING)) {
-        auto* new_req = new CallbackRequest<ServerContextType>(
-            req_->server_, req_->method_index_, req_->method_,
-            req_->method_tag_);
-        if (!new_req->Request()) {
-          // The server must have just decided to shutdown.
-          gpr_atm_no_barrier_fetch_add(
-              &new_req->server_
-                   ->callback_unmatched_reqs_count_[new_req->method_index_],
-              -1);
-          delete new_req;
-        }
-      }
-
-      // Bind the call, deadline, and metadata from what we got
-      req_->ctx_.set_call(req_->call_);
-      req_->ctx_.cq_ = req_->cq_;
-      req_->ctx_.BindDeadlineAndMetadata(req_->deadline_,
-                                         &req_->request_metadata_);
-      req_->request_metadata_.count = 0;
-
-      // Create a C++ Call to control the underlying core call
-      call_ = new (grpc_call_arena_alloc(req_->call_, sizeof(internal::Call)))
-          internal::Call(req_->call_, req_->server_, req_->cq_,
-                         req_->server_->max_receive_message_size(),
-                         req_->ctx_.set_server_rpc_info(
-                             req_->method_name(),
-                             (req_->method_ != nullptr)
-                                 ? req_->method_->method_type()
-                                 : internal::RpcMethod::BIDI_STREAMING,
-                             req_->server_->interceptor_creators_));
-
-      req_->interceptor_methods_.SetCall(call_);
-      req_->interceptor_methods_.SetReverse();
-      // Set interception point for RECV INITIAL METADATA
-      req_->interceptor_methods_.AddInterceptionHookPoint(
-          experimental::InterceptionHookPoints::POST_RECV_INITIAL_METADATA);
-      req_->interceptor_methods_.SetRecvInitialMetadata(
-          &req_->ctx_.client_metadata_);
-
-      if (req_->has_request_payload_) {
-        // Set interception point for RECV MESSAGE
-        req_->request_ = req_->method_->handler()->Deserialize(
-            req_->call_, req_->request_payload_, &req_->request_status_);
-        req_->request_payload_ = nullptr;
-        req_->interceptor_methods_.AddInterceptionHookPoint(
-            experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
-        req_->interceptor_methods_.SetRecvMessage(req_->request_, nullptr);
-      }
-
-      if (req_->interceptor_methods_.RunInterceptors(
-              [this] { ContinueRunAfterInterception(); })) {
-        ContinueRunAfterInterception();
-      } else {
-        // There were interceptors to be run, so ContinueRunAfterInterception
-        // will be run when interceptors are done.
-      }
-    }
-    void ContinueRunAfterInterception() {
-      auto* handler = (req_->method_ != nullptr)
-                          ? req_->method_->handler()
-                          : req_->server_->generic_handler_.get();
-      handler->RunHandler(internal::MethodHandler::HandlerParameter(
-          call_, &req_->ctx_, req_->request_, req_->request_status_, [this] {
-            // Recycle this request if there aren't too many outstanding.
-            // Note that we don't have to worry about a case where there
-            // are no requests waiting to match for this method since that
-            // is already taken care of when binding a request to a call.
-            // TODO(vjpai): Also don't recycle this request if the dynamic
-            //              load no longer justifies it. Consider measuring
-            //              dynamic load and setting a target accordingly.
-            if (req_->server_->callback_reqs_outstanding_ <
-                SOFT_MAXIMUM_CALLBACK_REQS_OUTSTANDING) {
-              req_->Clear();
-              req_->Setup();
-            } else {
-              // We can free up this request because there are too many
-              delete req_;
-              return;
-            }
-            if (!req_->Request()) {
-              // The server must have just decided to shutdown.
-              delete req_;
-            }
-          }));
-    }
-  };
-
-  void Clear() {
-    if (call_details_) {
-      delete call_details_;
-      call_details_ = nullptr;
-    }
-    grpc_metadata_array_destroy(&request_metadata_);
-    if (has_request_payload_ && request_payload_) {
-      grpc_byte_buffer_destroy(request_payload_);
-    }
-    ctx_.Clear();
-    interceptor_methods_.ClearState();
-  }
-
-  void Setup() {
-    gpr_atm_no_barrier_fetch_add(
-        &server_->callback_unmatched_reqs_count_[method_index_], 1);
-    grpc_metadata_array_init(&request_metadata_);
-    ctx_.Setup(gpr_inf_future(GPR_CLOCK_REALTIME));
-    request_payload_ = nullptr;
-    request_ = nullptr;
-    request_status_ = Status();
-  }
-
-  Server* const server_;
-  const size_t method_index_;
-  internal::RpcServiceMethod* const method_;
-  void* const method_tag_;
-  const bool has_request_payload_;
-  grpc_byte_buffer* request_payload_;
-  void* request_;
-  Status request_status_;
-  grpc_call_details* call_details_ = nullptr;
-  grpc_call* call_;
-  gpr_timespec deadline_;
-  grpc_metadata_array request_metadata_;
-  CompletionQueue* cq_;
-  CallbackCallTag tag_;
-  ServerContextType ctx_;
-  internal::InterceptorBatchMethodsImpl interceptor_methods_;
-};
-
-template <>
-bool Server::CallbackRequest<ServerContext>::FinalizeResult(void** tag,
-                                                            bool* status) {
-  return false;
-}
-
-template <>
-bool Server::CallbackRequest<GenericServerContext>::FinalizeResult(
-    void** tag, bool* status) {
-  if (*status) {
-    // TODO(yangg) remove the copy here
-    ctx_.method_ = StringFromCopiedSlice(call_details_->method);
-    ctx_.host_ = StringFromCopiedSlice(call_details_->host);
-  }
-  grpc_slice_unref(call_details_->method);
-  grpc_slice_unref(call_details_->host);
-  return false;
-}
-
-template <>
-const char* Server::CallbackRequest<ServerContext>::method_name() const {
-  return method_->name();
-}
-
-template <>
-const char* Server::CallbackRequest<GenericServerContext>::method_name() const {
-  return ctx_.method().c_str();
-}
-
-// Implementation of ThreadManager. Each instance of SyncRequestThreadManager
-// manages a pool of threads that poll for incoming Sync RPCs and call the
-// appropriate RPC handlers
-class Server::SyncRequestThreadManager : public ThreadManager {
- public:
-  SyncRequestThreadManager(Server* server, CompletionQueue* server_cq,
-                           std::shared_ptr<GlobalCallbacks> global_callbacks,
-                           grpc_resource_quota* rq, int min_pollers,
-                           int max_pollers, int cq_timeout_msec)
-      : ThreadManager("SyncServer", rq, min_pollers, max_pollers),
-        server_(server),
-        server_cq_(server_cq),
-        cq_timeout_msec_(cq_timeout_msec),
-        global_callbacks_(std::move(global_callbacks)) {}
-
-  WorkStatus PollForWork(void** tag, bool* ok) override {
-    *tag = nullptr;
-    // TODO(ctiller): workaround for GPR_TIMESPAN based deadlines not working
-    // right now
-    gpr_timespec deadline =
-        gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
-                     gpr_time_from_millis(cq_timeout_msec_, GPR_TIMESPAN));
-
-    switch (server_cq_->AsyncNext(tag, ok, deadline)) {
-      case CompletionQueue::TIMEOUT:
-        return TIMEOUT;
-      case CompletionQueue::SHUTDOWN:
-        return SHUTDOWN;
-      case CompletionQueue::GOT_EVENT:
-        return WORK_FOUND;
-    }
-
-    GPR_UNREACHABLE_CODE(return TIMEOUT);
-  }
-
-  void DoWork(void* tag, bool ok, bool resources) override {
-    SyncRequest* sync_req = static_cast<SyncRequest*>(tag);
-
-    if (!sync_req) {
-      // No tag. Nothing to work on. This is an unlikley scenario and possibly a
-      // bug in RPC Manager implementation.
-      gpr_log(GPR_ERROR, "Sync server. DoWork() was called with NULL tag");
-      return;
-    }
-
-    if (ok) {
-      // Calldata takes ownership of the completion queue and interceptors
-      // inside sync_req
-      auto* cd = new SyncRequest::CallData(server_, sync_req);
-      // Prepare for the next request
-      if (!IsShutdown()) {
-        sync_req->SetupRequest();  // Create new completion queue for sync_req
-        sync_req->Request(server_->c_server(), server_cq_->cq());
-      }
-
-      GPR_TIMER_SCOPE("cd.Run()", 0);
-      cd->Run(global_callbacks_, resources);
-    }
-    // TODO (sreek) If ok is false here (which it isn't in case of
-    // grpc_request_registered_call), we should still re-queue the request
-    // object
-  }
-
-  void AddSyncMethod(internal::RpcServiceMethod* method, void* tag) {
-    sync_requests_.emplace_back(new SyncRequest(method, tag));
-  }
-
-  void AddUnknownSyncMethod() {
-    if (!sync_requests_.empty()) {
-      unknown_method_.reset(new internal::RpcServiceMethod(
-          "unknown", internal::RpcMethod::BIDI_STREAMING,
-          new internal::UnknownMethodHandler));
-      sync_requests_.emplace_back(
-          new SyncRequest(unknown_method_.get(), nullptr));
-    }
-  }
-
-  void Shutdown() override {
-    ThreadManager::Shutdown();
-    server_cq_->Shutdown();
-  }
-
-  void Wait() override {
-    ThreadManager::Wait();
-    // Drain any pending items from the queue
-    void* tag;
-    bool ok;
-    while (server_cq_->Next(&tag, &ok)) {
-      if (ok) {
-        // If a request was pulled off the queue, it means that the thread
-        // handling the request added it to the completion queue after shutdown
-        // was called - because the thread had already started and checked the
-        // shutdown flag before shutdown was called. In this case, we simply
-        // clean it up here, *after* calling wait on all the worker threads, at
-        // which point we are certain no in-flight requests will add more to the
-        // queue. This fixes an intermittent memory leak on shutdown.
-        SyncRequest* sync_req = static_cast<SyncRequest*>(tag);
-        sync_req->PostShutdownCleanup();
-      }
-    }
-  }
-
-  void Start() {
-    if (!sync_requests_.empty()) {
-      for (auto m = sync_requests_.begin(); m != sync_requests_.end(); m++) {
-        (*m)->SetupRequest();
-        (*m)->Request(server_->c_server(), server_cq_->cq());
-      }
-
-      Initialize();  // ThreadManager's Initialize()
-    }
-  }
-
- private:
-  Server* server_;
-  CompletionQueue* server_cq_;
-  int cq_timeout_msec_;
-  std::vector<std::unique_ptr<SyncRequest>> sync_requests_;
-  std::unique_ptr<internal::RpcServiceMethod> unknown_method_;
-  std::shared_ptr<Server::GlobalCallbacks> global_callbacks_;
-};
-
-static internal::GrpcLibraryInitializer g_gli_initializer;
-Server::Server(
-    int max_receive_message_size, ChannelArguments* args,
-    std::shared_ptr<std::vector<std::unique_ptr<ServerCompletionQueue>>>
-        sync_server_cqs,
-    int min_pollers, int max_pollers, int sync_cq_timeout_msec,
-    grpc_resource_quota* server_rq,
-    std::vector<
-        std::unique_ptr<experimental::ServerInterceptorFactoryInterface>>
-        interceptor_creators)
-    : interceptor_creators_(std::move(interceptor_creators)),
-      max_receive_message_size_(max_receive_message_size),
-      sync_server_cqs_(std::move(sync_server_cqs)),
-      started_(false),
-      shutdown_(false),
-      shutdown_notified_(false),
-      server_(nullptr),
-      server_initializer_(new ServerInitializer(this)),
-      health_check_service_disabled_(false) {
-  g_gli_initializer.summon();
-  gpr_once_init(&g_once_init_callbacks, InitGlobalCallbacks);
-  global_callbacks_ = g_callbacks;
-  global_callbacks_->UpdateArguments(args);
-
-  if (sync_server_cqs_ != nullptr) {
-    bool default_rq_created = false;
-    if (server_rq == nullptr) {
-      server_rq = grpc_resource_quota_create("SyncServer-default-rq");
-      grpc_resource_quota_set_max_threads(server_rq,
-                                          DEFAULT_MAX_SYNC_SERVER_THREADS);
-      default_rq_created = true;
-    }
-
-    for (const auto& it : *sync_server_cqs_) {
-      sync_req_mgrs_.emplace_back(new SyncRequestThreadManager(
-          this, it.get(), global_callbacks_, server_rq, min_pollers,
-          max_pollers, sync_cq_timeout_msec));
-    }
-
-    if (default_rq_created) {
-      grpc_resource_quota_unref(server_rq);
-    }
-  }
-
-  grpc_channel_args channel_args;
-  args->SetChannelArgs(&channel_args);
-
-  for (size_t i = 0; i < channel_args.num_args; i++) {
-    if (0 ==
-        strcmp(channel_args.args[i].key, kHealthCheckServiceInterfaceArg)) {
-      if (channel_args.args[i].value.pointer.p == nullptr) {
-        health_check_service_disabled_ = true;
-      } else {
-        health_check_service_.reset(static_cast<HealthCheckServiceInterface*>(
-            channel_args.args[i].value.pointer.p));
-      }
-      break;
-    }
-  }
-
-  server_ = grpc_server_create(&channel_args, nullptr);
-}
-
-Server::~Server() {
-  {
-    std::unique_lock<std::mutex> lock(mu_);
-    if (callback_cq_ != nullptr) {
-      callback_cq_->Shutdown();
-    }
-    if (started_ && !shutdown_) {
-      lock.unlock();
-      Shutdown();
-    } else if (!started_) {
-      // Shutdown the completion queues
-      for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
-        (*it)->Shutdown();
-      }
-    }
-  }
-
-  grpc_server_destroy(server_);
-  for (auto& per_method_count : callback_unmatched_reqs_count_) {
-    // There should be no more unmatched callbacks for any method
-    // as each request is failed by Shutdown. Check that this actually
-    // happened
-    GPR_ASSERT(static_cast<int>(gpr_atm_no_barrier_load(&per_method_count)) ==
-               0);
-  }
-}
-
-void Server::SetGlobalCallbacks(GlobalCallbacks* callbacks) {
-  GPR_ASSERT(!g_callbacks);
-  GPR_ASSERT(callbacks);
-  g_callbacks.reset(callbacks);
-}
-
-grpc_server* Server::c_server() { return server_; }
-
-std::shared_ptr<Channel> Server::InProcessChannel(
-    const ChannelArguments& args) {
-  grpc_channel_args channel_args = args.c_channel_args();
-  return CreateChannelInternal(
-      "inproc", grpc_inproc_channel_create(server_, &channel_args, nullptr),
-      std::vector<
-          std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>());
-}
-
-std::shared_ptr<Channel>
-Server::experimental_type::InProcessChannelWithInterceptors(
-    const ChannelArguments& args,
-    std::vector<
-        std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
-        interceptor_creators) {
-  grpc_channel_args channel_args = args.c_channel_args();
-  return CreateChannelInternal(
-      "inproc",
-      grpc_inproc_channel_create(server_->server_, &channel_args, nullptr),
-      std::move(interceptor_creators));
-}
-
-static grpc_server_register_method_payload_handling PayloadHandlingForMethod(
-    internal::RpcServiceMethod* method) {
-  switch (method->method_type()) {
-    case internal::RpcMethod::NORMAL_RPC:
-    case internal::RpcMethod::SERVER_STREAMING:
-      return GRPC_SRM_PAYLOAD_READ_INITIAL_BYTE_BUFFER;
-    case internal::RpcMethod::CLIENT_STREAMING:
-    case internal::RpcMethod::BIDI_STREAMING:
-      return GRPC_SRM_PAYLOAD_NONE;
-  }
-  GPR_UNREACHABLE_CODE(return GRPC_SRM_PAYLOAD_NONE;);
-}
-
-bool Server::RegisterService(const grpc::string* host, Service* service) {
-  bool has_async_methods = service->has_async_methods();
-  if (has_async_methods) {
-    GPR_ASSERT(service->server_ == nullptr &&
-               "Can only register an asynchronous service against one server.");
-    service->server_ = this;
-  }
-
-  const char* method_name = nullptr;
-
-  for (auto it = service->methods_.begin(); it != service->methods_.end();
-       ++it) {
-    if (it->get() == nullptr) {  // Handled by generic service if any.
-      continue;
-    }
-
-    internal::RpcServiceMethod* method = it->get();
-    void* method_registration_tag = grpc_server_register_method(
-        server_, method->name(), host ? host->c_str() : nullptr,
-        PayloadHandlingForMethod(method), 0);
-    if (method_registration_tag == nullptr) {
-      gpr_log(GPR_DEBUG, "Attempt to register %s multiple times",
-              method->name());
-      return false;
-    }
-
-    if (method->handler() == nullptr) {  // Async method without handler
-      method->set_server_tag(method_registration_tag);
-    } else if (method->api_type() ==
-               internal::RpcServiceMethod::ApiType::SYNC) {
-      for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
-        (*it)->AddSyncMethod(method, method_registration_tag);
-      }
-    } else {
-      // a callback method. Register at least some callback requests
-      callback_unmatched_reqs_count_.push_back(0);
-      auto method_index = callback_unmatched_reqs_count_.size() - 1;
-      // TODO(vjpai): Register these dynamically based on need
-      for (int i = 0; i < DEFAULT_CALLBACK_REQS_PER_METHOD; i++) {
-        callback_reqs_to_start_.push_back(new CallbackRequest<ServerContext>(
-            this, method_index, method, method_registration_tag));
-      }
-      // Enqueue it so that it will be Request'ed later after all request
-      // matchers are created at core server startup
-    }
-
-    method_name = method->name();
-  }
-
-  // Parse service name.
-  if (method_name != nullptr) {
-    std::stringstream ss(method_name);
-    grpc::string service_name;
-    if (std::getline(ss, service_name, '/') &&
-        std::getline(ss, service_name, '/')) {
-      services_.push_back(service_name);
-    }
-  }
-  return true;
-}
-
-void Server::RegisterAsyncGenericService(AsyncGenericService* service) {
-  GPR_ASSERT(service->server_ == nullptr &&
-             "Can only register an async generic service against one server.");
-  service->server_ = this;
-  has_async_generic_service_ = true;
-}
-
-void Server::RegisterCallbackGenericService(
-    experimental::CallbackGenericService* service) {
-  GPR_ASSERT(
-      service->server_ == nullptr &&
-      "Can only register a callback generic service against one server.");
-  service->server_ = this;
-  has_callback_generic_service_ = true;
-  generic_handler_.reset(service->Handler());
-
-  callback_unmatched_reqs_count_.push_back(0);
-  auto method_index = callback_unmatched_reqs_count_.size() - 1;
-  // TODO(vjpai): Register these dynamically based on need
-  for (int i = 0; i < DEFAULT_CALLBACK_REQS_PER_METHOD; i++) {
-    callback_reqs_to_start_.push_back(new CallbackRequest<GenericServerContext>(
-        this, method_index, nullptr, nullptr));
-  }
-}
-
-int Server::AddListeningPort(const grpc::string& addr,
-                             ServerCredentials* creds) {
-  GPR_ASSERT(!started_);
-  int port = creds->AddPortToServer(addr, server_);
-  global_callbacks_->AddPort(this, addr, creds, port);
-  return port;
-}
-
-void Server::Start(ServerCompletionQueue** cqs, size_t num_cqs) {
-  GPR_ASSERT(!started_);
-  global_callbacks_->PreServerStart(this);
-  started_ = true;
-
-  // Only create default health check service when user did not provide an
-  // explicit one.
-  ServerCompletionQueue* health_check_cq = nullptr;
-  DefaultHealthCheckService::HealthCheckServiceImpl*
-      default_health_check_service_impl = nullptr;
-  if (health_check_service_ == nullptr && !health_check_service_disabled_ &&
-      DefaultHealthCheckServiceEnabled()) {
-    auto* default_hc_service = new DefaultHealthCheckService;
-    health_check_service_.reset(default_hc_service);
-    // We create a non-polling CQ to avoid impacting application
-    // performance.  This ensures that we don't introduce thread hops
-    // for application requests that wind up on this CQ, which is polled
-    // in its own thread.
-    health_check_cq =
-        new ServerCompletionQueue(GRPC_CQ_NEXT, GRPC_CQ_NON_POLLING, nullptr);
-    grpc_server_register_completion_queue(server_, health_check_cq->cq(),
-                                          nullptr);
-    default_health_check_service_impl =
-        default_hc_service->GetHealthCheckService(
-            std::unique_ptr<ServerCompletionQueue>(health_check_cq));
-    RegisterService(nullptr, default_health_check_service_impl);
-  }
-
-  // If this server uses callback methods, then create a callback generic
-  // service to handle any unimplemented methods using the default reactor
-  // creator
-  if (!callback_reqs_to_start_.empty() && !has_callback_generic_service_) {
-    unimplemented_service_.reset(new experimental::CallbackGenericService);
-    RegisterCallbackGenericService(unimplemented_service_.get());
-  }
-
-  grpc_server_start(server_);
-
-  if (!has_async_generic_service_ && !has_callback_generic_service_) {
-    for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
-      (*it)->AddUnknownSyncMethod();
-    }
-
-    for (size_t i = 0; i < num_cqs; i++) {
-      if (cqs[i]->IsFrequentlyPolled()) {
-        new UnimplementedAsyncRequest(this, cqs[i]);
-      }
-    }
-    if (health_check_cq != nullptr) {
-      new UnimplementedAsyncRequest(this, health_check_cq);
-    }
-  }
-
-  // If this server has any support for synchronous methods (has any sync
-  // server CQs), make sure that we have a ResourceExhausted handler
-  // to deal with the case of thread exhaustion
-  if (sync_server_cqs_ != nullptr && !sync_server_cqs_->empty()) {
-    resource_exhausted_handler_.reset(new internal::ResourceExhaustedHandler);
-  }
-
-  for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
-    (*it)->Start();
-  }
-
-  for (auto* cbreq : callback_reqs_to_start_) {
-    GPR_ASSERT(cbreq->Request());
-  }
-  callback_reqs_to_start_.clear();
-
-  if (default_health_check_service_impl != nullptr) {
-    default_health_check_service_impl->StartServingThread();
-  }
-}
-
-void Server::ShutdownInternal(gpr_timespec deadline) {
-  std::unique_lock<std::mutex> lock(mu_);
-  if (shutdown_) {
-    return;
-  }
-
-  shutdown_ = true;
-
-  /// The completion queue to use for server shutdown completion notification
-  CompletionQueue shutdown_cq;
-  ShutdownTag shutdown_tag;  // Dummy shutdown tag
-  grpc_server_shutdown_and_notify(server_, shutdown_cq.cq(), &shutdown_tag);
-
-  shutdown_cq.Shutdown();
-
-  void* tag;
-  bool ok;
-  CompletionQueue::NextStatus status =
-      shutdown_cq.AsyncNext(&tag, &ok, deadline);
-
-  // If this timed out, it means we are done with the grace period for a clean
-  // shutdown. We should force a shutdown now by cancelling all inflight calls
-  if (status == CompletionQueue::NextStatus::TIMEOUT) {
-    grpc_server_cancel_all_calls(server_);
-  }
-  // Else in case of SHUTDOWN or GOT_EVENT, it means that the server has
-  // successfully shutdown
-
-  // Shutdown all ThreadManagers. This will try to gracefully stop all the
-  // threads in the ThreadManagers (once they process any inflight requests)
-  for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
-    (*it)->Shutdown();  // ThreadManager's Shutdown()
-  }
-
-  // Wait for threads in all ThreadManagers to terminate
-  for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
-    (*it)->Wait();
-  }
-
-  // Wait for all outstanding callback requests to complete
-  // (whether waiting for a match or already active).
-  // We know that no new requests will be created after this point
-  // because they are only created at server startup time or when
-  // we have a successful match on a request. During the shutdown phase,
-  // requests that have not yet matched will be failed rather than
-  // allowed to succeed, which will cause the server to delete the
-  // request and decrement the count. Possibly a request will match before
-  // the shutdown but then find that shutdown has already started by the
-  // time it tries to register a new request. In that case, the registration
-  // will report a failure, indicating a shutdown and again we won't end
-  // up incrementing the counter.
-  {
-    std::unique_lock<std::mutex> cblock(callback_reqs_mu_);
-    callback_reqs_done_cv_.wait(
-        cblock, [this] { return callback_reqs_outstanding_ == 0; });
-  }
-
-  // Drain the shutdown queue (if the previous call to AsyncNext() timed out
-  // and we didn't remove the tag from the queue yet)
-  while (shutdown_cq.Next(&tag, &ok)) {
-    // Nothing to be done here. Just ignore ok and tag values
-  }
-
-  shutdown_notified_ = true;
-  shutdown_cv_.notify_all();
-}
-
-void Server::Wait() {
-  std::unique_lock<std::mutex> lock(mu_);
-  while (started_ && !shutdown_notified_) {
-    shutdown_cv_.wait(lock);
-  }
-}
-
-void Server::PerformOpsOnCall(internal::CallOpSetInterface* ops,
-                              internal::Call* call) {
-  ops->FillOps(call);
-}
-
 ServerInterface::BaseAsyncRequest::BaseAsyncRequest(
     ServerInterface* server, ServerContext* context,
     internal::ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq,
@@ -1272,32 +249,6 @@
   return BaseAsyncRequest::FinalizeResult(tag, status);
 }
 
-bool Server::UnimplementedAsyncRequest::FinalizeResult(void** tag,
-                                                       bool* status) {
-  if (GenericAsyncRequest::FinalizeResult(tag, status)) {
-    // We either had no interceptors run or we are done intercepting
-    if (*status) {
-      new UnimplementedAsyncRequest(server_, cq_);
-      new UnimplementedAsyncResponse(this);
-    } else {
-      delete this;
-    }
-  } else {
-    // The tag was swallowed due to interception. We will see it again.
-  }
-  return false;
-}
-
-Server::UnimplementedAsyncResponse::UnimplementedAsyncResponse(
-    UnimplementedAsyncRequest* request)
-    : request_(request) {
-  Status status(StatusCode::UNIMPLEMENTED, "");
-  internal::UnknownMethodHandler::FillOps(request_->context(), this);
-  request_->stream()->call_.PerformOps(this);
-}
-
-ServerInitializer* Server::initializer() { return server_initializer_.get(); }
-
 namespace {
 class ShutdownCallback : public grpc_experimental_completion_queue_functor {
  public:
@@ -1319,13 +270,1086 @@
 };
 }  // namespace
 
-CompletionQueue* Server::CallbackCQ() {
+}  // namespace grpc
+
+namespace grpc_impl {
+
+/// Use private inheritance rather than composition only to establish order
+/// of construction, since the public base class should be constructed after the
+/// elements belonging to the private base class are constructed. This is not
+/// possible using true composition.
+class Server::UnimplementedAsyncRequest final
+    : private grpc::UnimplementedAsyncRequestContext,
+      public GenericAsyncRequest {
+ public:
+  UnimplementedAsyncRequest(Server* server, grpc::ServerCompletionQueue* cq)
+      : GenericAsyncRequest(server, &server_context_, &generic_stream_, cq, cq,
+                            nullptr, false),
+        server_(server),
+        cq_(cq) {}
+
+  bool FinalizeResult(void** tag, bool* status) override;
+
+  grpc::ServerContext* context() { return &server_context_; }
+  grpc::GenericServerAsyncReaderWriter* stream() { return &generic_stream_; }
+
+ private:
+  Server* const server_;
+  grpc::ServerCompletionQueue* const cq_;
+};
+
+/// UnimplementedAsyncResponse should not post user-visible completions to the
+/// C++ completion queue, but is generated as a CQ event by the core
+class Server::UnimplementedAsyncResponse final
+    : public grpc::internal::CallOpSet<
+          grpc::internal::CallOpSendInitialMetadata,
+          grpc::internal::CallOpServerSendStatus> {
+ public:
+  UnimplementedAsyncResponse(UnimplementedAsyncRequest* request);
+  ~UnimplementedAsyncResponse() { delete request_; }
+
+  bool FinalizeResult(void** tag, bool* status) override {
+    if (grpc::internal::CallOpSet<
+            grpc::internal::CallOpSendInitialMetadata,
+            grpc::internal::CallOpServerSendStatus>::FinalizeResult(tag,
+                                                                    status)) {
+      delete this;
+    } else {
+      // The tag was swallowed due to interception. We will see it again.
+    }
+    return false;
+  }
+
+ private:
+  UnimplementedAsyncRequest* const request_;
+};
+
+class Server::SyncRequest final : public grpc::internal::CompletionQueueTag {
+ public:
+  SyncRequest(grpc::internal::RpcServiceMethod* method, void* method_tag)
+      : method_(method),
+        method_tag_(method_tag),
+        in_flight_(false),
+        has_request_payload_(method->method_type() ==
+                                 grpc::internal::RpcMethod::NORMAL_RPC ||
+                             method->method_type() ==
+                                 grpc::internal::RpcMethod::SERVER_STREAMING),
+        call_details_(nullptr),
+        cq_(nullptr) {
+    grpc_metadata_array_init(&request_metadata_);
+  }
+
+  ~SyncRequest() {
+    if (call_details_) {
+      delete call_details_;
+    }
+    grpc_metadata_array_destroy(&request_metadata_);
+  }
+
+  void SetupRequest() { cq_ = grpc_completion_queue_create_for_pluck(nullptr); }
+
+  void TeardownRequest() {
+    grpc_completion_queue_destroy(cq_);
+    cq_ = nullptr;
+  }
+
+  void Request(grpc_server* server, grpc_completion_queue* notify_cq) {
+    GPR_ASSERT(cq_ && !in_flight_);
+    in_flight_ = true;
+    if (method_tag_) {
+      if (grpc_server_request_registered_call(
+              server, method_tag_, &call_, &deadline_, &request_metadata_,
+              has_request_payload_ ? &request_payload_ : nullptr, cq_,
+              notify_cq, this) != GRPC_CALL_OK) {
+        TeardownRequest();
+        return;
+      }
+    } else {
+      if (!call_details_) {
+        call_details_ = new grpc_call_details;
+        grpc_call_details_init(call_details_);
+      }
+      if (grpc_server_request_call(server, &call_, call_details_,
+                                   &request_metadata_, cq_, notify_cq,
+                                   this) != GRPC_CALL_OK) {
+        TeardownRequest();
+        return;
+      }
+    }
+  }
+
+  void PostShutdownCleanup() {
+    if (call_) {
+      grpc_call_unref(call_);
+      call_ = nullptr;
+    }
+    if (cq_) {
+      grpc_completion_queue_destroy(cq_);
+      cq_ = nullptr;
+    }
+  }
+
+  bool FinalizeResult(void** tag, bool* status) override {
+    if (!*status) {
+      grpc_completion_queue_destroy(cq_);
+      cq_ = nullptr;
+    }
+    if (call_details_) {
+      deadline_ = call_details_->deadline;
+      grpc_call_details_destroy(call_details_);
+      grpc_call_details_init(call_details_);
+    }
+    return true;
+  }
+
+  // The CallData class represents a call that is "active" as opposed
+  // to just being requested. It wraps and takes ownership of the cq from
+  // the call request
+  class CallData final {
+   public:
+    explicit CallData(Server* server, SyncRequest* mrd)
+        : cq_(mrd->cq_),
+          ctx_(mrd->deadline_, &mrd->request_metadata_),
+          has_request_payload_(mrd->has_request_payload_),
+          request_payload_(has_request_payload_ ? mrd->request_payload_
+                                                : nullptr),
+          request_(nullptr),
+          method_(mrd->method_),
+          call_(
+              mrd->call_, server, &cq_, server->max_receive_message_size(),
+              ctx_.set_server_rpc_info(method_->name(), method_->method_type(),
+                                       server->interceptor_creators_)),
+          server_(server),
+          global_callbacks_(nullptr),
+          resources_(false) {
+      ctx_.set_call(mrd->call_);
+      ctx_.cq_ = &cq_;
+      GPR_ASSERT(mrd->in_flight_);
+      mrd->in_flight_ = false;
+      mrd->request_metadata_.count = 0;
+    }
+
+    ~CallData() {
+      if (has_request_payload_ && request_payload_) {
+        grpc_byte_buffer_destroy(request_payload_);
+      }
+    }
+
+    void Run(const std::shared_ptr<GlobalCallbacks>& global_callbacks,
+             bool resources) {
+      global_callbacks_ = global_callbacks;
+      resources_ = resources;
+
+      interceptor_methods_.SetCall(&call_);
+      interceptor_methods_.SetReverse();
+      // Set interception point for RECV INITIAL METADATA
+      interceptor_methods_.AddInterceptionHookPoint(
+          grpc::experimental::InterceptionHookPoints::
+              POST_RECV_INITIAL_METADATA);
+      interceptor_methods_.SetRecvInitialMetadata(&ctx_.client_metadata_);
+
+      if (has_request_payload_) {
+        // Set interception point for RECV MESSAGE
+        auto* handler = resources_ ? method_->handler()
+                                   : server_->resource_exhausted_handler_.get();
+        request_ = handler->Deserialize(call_.call(), request_payload_,
+                                        &request_status_, nullptr);
+
+        request_payload_ = nullptr;
+        interceptor_methods_.AddInterceptionHookPoint(
+            grpc::experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
+        interceptor_methods_.SetRecvMessage(request_, nullptr);
+      }
+
+      if (interceptor_methods_.RunInterceptors(
+              [this]() { ContinueRunAfterInterception(); })) {
+        ContinueRunAfterInterception();
+      } else {
+        // There were interceptors to be run, so ContinueRunAfterInterception
+        // will be run when interceptors are done.
+      }
+    }
+
+    void ContinueRunAfterInterception() {
+      {
+        ctx_.BeginCompletionOp(&call_, nullptr, nullptr);
+        global_callbacks_->PreSynchronousRequest(&ctx_);
+        auto* handler = resources_ ? method_->handler()
+                                   : server_->resource_exhausted_handler_.get();
+        handler->RunHandler(grpc::internal::MethodHandler::HandlerParameter(
+            &call_, &ctx_, request_, request_status_, nullptr, nullptr));
+        request_ = nullptr;
+        global_callbacks_->PostSynchronousRequest(&ctx_);
+
+        cq_.Shutdown();
+
+        grpc::internal::CompletionQueueTag* op_tag = ctx_.GetCompletionOpTag();
+        cq_.TryPluck(op_tag, gpr_inf_future(GPR_CLOCK_REALTIME));
+
+        /* Ensure the cq_ is shutdown */
+        grpc::DummyTag ignored_tag;
+        GPR_ASSERT(cq_.Pluck(&ignored_tag) == false);
+      }
+      delete this;
+    }
+
+   private:
+    grpc::CompletionQueue cq_;
+    grpc::ServerContext ctx_;
+    const bool has_request_payload_;
+    grpc_byte_buffer* request_payload_;
+    void* request_;
+    grpc::Status request_status_;
+    grpc::internal::RpcServiceMethod* const method_;
+    grpc::internal::Call call_;
+    Server* server_;
+    std::shared_ptr<GlobalCallbacks> global_callbacks_;
+    bool resources_;
+    grpc::internal::InterceptorBatchMethodsImpl interceptor_methods_;
+  };
+
+ private:
+  grpc::internal::RpcServiceMethod* const method_;
+  void* const method_tag_;
+  bool in_flight_;
+  const bool has_request_payload_;
+  grpc_call* call_;
+  grpc_call_details* call_details_;
+  gpr_timespec deadline_;
+  grpc_metadata_array request_metadata_;
+  grpc_byte_buffer* request_payload_;
+  grpc_completion_queue* cq_;
+};
+
+class Server::CallbackRequestBase : public grpc::internal::CompletionQueueTag {
+ public:
+  virtual ~CallbackRequestBase() {}
+  virtual bool Request() = 0;
+};
+
+template <class ServerContextType>
+class Server::CallbackRequest final : public Server::CallbackRequestBase {
+ public:
+  static_assert(std::is_base_of<grpc::ServerContext, ServerContextType>::value,
+                "ServerContextType must be derived from ServerContext");
+
+  // The constructor needs to know the server for this callback request and its
+  // index in the server's request count array to allow for proper dynamic
+  // requesting of incoming RPCs. For codegen services, the values of method and
+  // method_tag represent the defined characteristics of the method being
+  // requested. For generic services, method and method_tag are nullptr since
+  // these services don't have pre-defined methods or method registration tags.
+  CallbackRequest(Server* server, size_t method_idx,
+                  grpc::internal::RpcServiceMethod* method, void* method_tag)
+      : server_(server),
+        method_index_(method_idx),
+        method_(method),
+        method_tag_(method_tag),
+        has_request_payload_(
+            method_ != nullptr &&
+            (method->method_type() == grpc::internal::RpcMethod::NORMAL_RPC ||
+             method->method_type() ==
+                 grpc::internal::RpcMethod::SERVER_STREAMING)),
+        cq_(server->CallbackCQ()),
+        tag_(this) {
+    server_->callback_reqs_outstanding_++;
+    Setup();
+  }
+
+  ~CallbackRequest() {
+    Clear();
+
+    // The counter of outstanding requests must be decremented
+    // under a lock in case it causes the server shutdown.
+    grpc::internal::MutexLock l(&server_->callback_reqs_mu_);
+    if (--server_->callback_reqs_outstanding_ == 0) {
+      server_->callback_reqs_done_cv_.Signal();
+    }
+  }
+
+  bool Request() override {
+    if (method_tag_) {
+      if (GRPC_CALL_OK !=
+          grpc_server_request_registered_call(
+              server_->c_server(), method_tag_, &call_, &deadline_,
+              &request_metadata_,
+              has_request_payload_ ? &request_payload_ : nullptr, cq_->cq(),
+              cq_->cq(), static_cast<void*>(&tag_))) {
+        return false;
+      }
+    } else {
+      if (!call_details_) {
+        call_details_ = new grpc_call_details;
+        grpc_call_details_init(call_details_);
+      }
+      if (grpc_server_request_call(server_->c_server(), &call_, call_details_,
+                                   &request_metadata_, cq_->cq(), cq_->cq(),
+                                   static_cast<void*>(&tag_)) != GRPC_CALL_OK) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // Needs specialization to account for different processing of metadata
+  // in generic API
+  bool FinalizeResult(void** tag, bool* status) override;
+
+ private:
+  // method_name needs to be specialized between named method and generic
+  const char* method_name() const;
+
+  class CallbackCallTag : public grpc_experimental_completion_queue_functor {
+   public:
+    CallbackCallTag(Server::CallbackRequest<ServerContextType>* req)
+        : req_(req) {
+      functor_run = &CallbackCallTag::StaticRun;
+    }
+
+    // force_run can not be performed on a tag if operations using this tag
+    // have been sent to PerformOpsOnCall. It is intended for error conditions
+    // that are detected before the operations are internally processed.
+    void force_run(bool ok) { Run(ok); }
+
+   private:
+    Server::CallbackRequest<ServerContextType>* req_;
+    grpc::internal::Call* call_;
+
+    static void StaticRun(grpc_experimental_completion_queue_functor* cb,
+                          int ok) {
+      static_cast<CallbackCallTag*>(cb)->Run(static_cast<bool>(ok));
+    }
+    void Run(bool ok) {
+      void* ignored = req_;
+      bool new_ok = ok;
+      GPR_ASSERT(!req_->FinalizeResult(&ignored, &new_ok));
+      GPR_ASSERT(ignored == req_);
+
+      int count =
+          static_cast<int>(gpr_atm_no_barrier_fetch_add(
+              &req_->server_
+                   ->callback_unmatched_reqs_count_[req_->method_index_],
+              -1)) -
+          1;
+      if (!ok) {
+        // The call has been shutdown.
+        // Delete its contents to free up the request.
+        delete req_;
+        return;
+      }
+
+      // If this was the last request in the list or it is below the soft
+      // minimum and there are spare requests available, set up a new one.
+      if (count == 0 || (count < SOFT_MINIMUM_SPARE_CALLBACK_REQS_PER_METHOD &&
+                         req_->server_->callback_reqs_outstanding_ <
+                             SOFT_MAXIMUM_CALLBACK_REQS_OUTSTANDING)) {
+        auto* new_req = new CallbackRequest<ServerContextType>(
+            req_->server_, req_->method_index_, req_->method_,
+            req_->method_tag_);
+        if (!new_req->Request()) {
+          // The server must have just decided to shutdown.
+          gpr_atm_no_barrier_fetch_add(
+              &new_req->server_
+                   ->callback_unmatched_reqs_count_[new_req->method_index_],
+              -1);
+          delete new_req;
+        }
+      }
+
+      // Bind the call, deadline, and metadata from what we got
+      req_->ctx_.set_call(req_->call_);
+      req_->ctx_.cq_ = req_->cq_;
+      req_->ctx_.BindDeadlineAndMetadata(req_->deadline_,
+                                         &req_->request_metadata_);
+      req_->request_metadata_.count = 0;
+
+      // Create a C++ Call to control the underlying core call
+      call_ =
+          new (grpc_call_arena_alloc(req_->call_, sizeof(grpc::internal::Call)))
+              grpc::internal::Call(
+                  req_->call_, req_->server_, req_->cq_,
+                  req_->server_->max_receive_message_size(),
+                  req_->ctx_.set_server_rpc_info(
+                      req_->method_name(),
+                      (req_->method_ != nullptr)
+                          ? req_->method_->method_type()
+                          : grpc::internal::RpcMethod::BIDI_STREAMING,
+                      req_->server_->interceptor_creators_));
+
+      req_->interceptor_methods_.SetCall(call_);
+      req_->interceptor_methods_.SetReverse();
+      // Set interception point for RECV INITIAL METADATA
+      req_->interceptor_methods_.AddInterceptionHookPoint(
+          grpc::experimental::InterceptionHookPoints::
+              POST_RECV_INITIAL_METADATA);
+      req_->interceptor_methods_.SetRecvInitialMetadata(
+          &req_->ctx_.client_metadata_);
+
+      if (req_->has_request_payload_) {
+        // Set interception point for RECV MESSAGE
+        req_->request_ = req_->method_->handler()->Deserialize(
+            req_->call_, req_->request_payload_, &req_->request_status_,
+            &req_->handler_data_);
+        req_->request_payload_ = nullptr;
+        req_->interceptor_methods_.AddInterceptionHookPoint(
+            grpc::experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
+        req_->interceptor_methods_.SetRecvMessage(req_->request_, nullptr);
+      }
+
+      if (req_->interceptor_methods_.RunInterceptors(
+              [this] { ContinueRunAfterInterception(); })) {
+        ContinueRunAfterInterception();
+      } else {
+        // There were interceptors to be run, so ContinueRunAfterInterception
+        // will be run when interceptors are done.
+      }
+    }
+    void ContinueRunAfterInterception() {
+      auto* handler = (req_->method_ != nullptr)
+                          ? req_->method_->handler()
+                          : req_->server_->generic_handler_.get();
+      handler->RunHandler(grpc::internal::MethodHandler::HandlerParameter(
+          call_, &req_->ctx_, req_->request_, req_->request_status_,
+          req_->handler_data_, [this] {
+            // Recycle this request if there aren't too many outstanding.
+            // Note that we don't have to worry about a case where there
+            // are no requests waiting to match for this method since that
+            // is already taken care of when binding a request to a call.
+            // TODO(vjpai): Also don't recycle this request if the dynamic
+            //              load no longer justifies it. Consider measuring
+            //              dynamic load and setting a target accordingly.
+            if (req_->server_->callback_reqs_outstanding_ <
+                SOFT_MAXIMUM_CALLBACK_REQS_OUTSTANDING) {
+              req_->Clear();
+              req_->Setup();
+            } else {
+              // We can free up this request because there are too many
+              delete req_;
+              return;
+            }
+            if (!req_->Request()) {
+              // The server must have just decided to shutdown.
+              delete req_;
+            }
+          }));
+    }
+  };
+
+  void Clear() {
+    if (call_details_) {
+      delete call_details_;
+      call_details_ = nullptr;
+    }
+    grpc_metadata_array_destroy(&request_metadata_);
+    if (has_request_payload_ && request_payload_) {
+      grpc_byte_buffer_destroy(request_payload_);
+    }
+    ctx_.Clear();
+    interceptor_methods_.ClearState();
+  }
+
+  void Setup() {
+    gpr_atm_no_barrier_fetch_add(
+        &server_->callback_unmatched_reqs_count_[method_index_], 1);
+    grpc_metadata_array_init(&request_metadata_);
+    ctx_.Setup(gpr_inf_future(GPR_CLOCK_REALTIME));
+    request_payload_ = nullptr;
+    request_ = nullptr;
+    handler_data_ = nullptr;
+    request_status_ = grpc::Status();
+  }
+
+  Server* const server_;
+  const size_t method_index_;
+  grpc::internal::RpcServiceMethod* const method_;
+  void* const method_tag_;
+  const bool has_request_payload_;
+  grpc_byte_buffer* request_payload_;
+  void* request_;
+  void* handler_data_;
+  grpc::Status request_status_;
+  grpc_call_details* call_details_ = nullptr;
+  grpc_call* call_;
+  gpr_timespec deadline_;
+  grpc_metadata_array request_metadata_;
+  grpc::CompletionQueue* cq_;
+  CallbackCallTag tag_;
+  ServerContextType ctx_;
+  grpc::internal::InterceptorBatchMethodsImpl interceptor_methods_;
+};
+
+template <>
+bool Server::CallbackRequest<grpc::ServerContext>::FinalizeResult(
+    void** tag, bool* status) {
+  return false;
+}
+
+template <>
+bool Server::CallbackRequest<grpc::GenericServerContext>::FinalizeResult(
+    void** tag, bool* status) {
+  if (*status) {
+    // TODO(yangg) remove the copy here
+    ctx_.method_ = grpc::StringFromCopiedSlice(call_details_->method);
+    ctx_.host_ = grpc::StringFromCopiedSlice(call_details_->host);
+  }
+  grpc_slice_unref(call_details_->method);
+  grpc_slice_unref(call_details_->host);
+  return false;
+}
+
+template <>
+const char* Server::CallbackRequest<grpc::ServerContext>::method_name() const {
+  return method_->name();
+}
+
+template <>
+const char* Server::CallbackRequest<grpc::GenericServerContext>::method_name()
+    const {
+  return ctx_.method().c_str();
+}
+
+// Implementation of ThreadManager. Each instance of SyncRequestThreadManager
+// manages a pool of threads that poll for incoming Sync RPCs and call the
+// appropriate RPC handlers
+class Server::SyncRequestThreadManager : public grpc::ThreadManager {
+ public:
+  SyncRequestThreadManager(Server* server, grpc::CompletionQueue* server_cq,
+                           std::shared_ptr<GlobalCallbacks> global_callbacks,
+                           grpc_resource_quota* rq, int min_pollers,
+                           int max_pollers, int cq_timeout_msec)
+      : ThreadManager("SyncServer", rq, min_pollers, max_pollers),
+        server_(server),
+        server_cq_(server_cq),
+        cq_timeout_msec_(cq_timeout_msec),
+        global_callbacks_(std::move(global_callbacks)) {}
+
+  WorkStatus PollForWork(void** tag, bool* ok) override {
+    *tag = nullptr;
+    // TODO(ctiller): workaround for GPR_TIMESPAN based deadlines not working
+    // right now
+    gpr_timespec deadline =
+        gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+                     gpr_time_from_millis(cq_timeout_msec_, GPR_TIMESPAN));
+
+    switch (server_cq_->AsyncNext(tag, ok, deadline)) {
+      case grpc::CompletionQueue::TIMEOUT:
+        return TIMEOUT;
+      case grpc::CompletionQueue::SHUTDOWN:
+        return SHUTDOWN;
+      case grpc::CompletionQueue::GOT_EVENT:
+        return WORK_FOUND;
+    }
+
+    GPR_UNREACHABLE_CODE(return TIMEOUT);
+  }
+
+  void DoWork(void* tag, bool ok, bool resources) override {
+    SyncRequest* sync_req = static_cast<SyncRequest*>(tag);
+
+    if (!sync_req) {
+      // No tag. Nothing to work on. This is an unlikley scenario and possibly a
+      // bug in RPC Manager implementation.
+      gpr_log(GPR_ERROR, "Sync server. DoWork() was called with NULL tag");
+      return;
+    }
+
+    if (ok) {
+      // Calldata takes ownership of the completion queue and interceptors
+      // inside sync_req
+      auto* cd = new SyncRequest::CallData(server_, sync_req);
+      // Prepare for the next request
+      if (!IsShutdown()) {
+        sync_req->SetupRequest();  // Create new completion queue for sync_req
+        sync_req->Request(server_->c_server(), server_cq_->cq());
+      }
+
+      GPR_TIMER_SCOPE("cd.Run()", 0);
+      cd->Run(global_callbacks_, resources);
+    }
+    // TODO (sreek) If ok is false here (which it isn't in case of
+    // grpc_request_registered_call), we should still re-queue the request
+    // object
+  }
+
+  void AddSyncMethod(grpc::internal::RpcServiceMethod* method, void* tag) {
+    sync_requests_.emplace_back(new SyncRequest(method, tag));
+  }
+
+  void AddUnknownSyncMethod() {
+    if (!sync_requests_.empty()) {
+      unknown_method_.reset(new grpc::internal::RpcServiceMethod(
+          "unknown", grpc::internal::RpcMethod::BIDI_STREAMING,
+          new grpc::internal::UnknownMethodHandler));
+      sync_requests_.emplace_back(
+          new SyncRequest(unknown_method_.get(), nullptr));
+    }
+  }
+
+  void Shutdown() override {
+    ThreadManager::Shutdown();
+    server_cq_->Shutdown();
+  }
+
+  void Wait() override {
+    ThreadManager::Wait();
+    // Drain any pending items from the queue
+    void* tag;
+    bool ok;
+    while (server_cq_->Next(&tag, &ok)) {
+      if (ok) {
+        // If a request was pulled off the queue, it means that the thread
+        // handling the request added it to the completion queue after shutdown
+        // was called - because the thread had already started and checked the
+        // shutdown flag before shutdown was called. In this case, we simply
+        // clean it up here, *after* calling wait on all the worker threads, at
+        // which point we are certain no in-flight requests will add more to the
+        // queue. This fixes an intermittent memory leak on shutdown.
+        SyncRequest* sync_req = static_cast<SyncRequest*>(tag);
+        sync_req->PostShutdownCleanup();
+      }
+    }
+  }
+
+  void Start() {
+    if (!sync_requests_.empty()) {
+      for (auto m = sync_requests_.begin(); m != sync_requests_.end(); m++) {
+        (*m)->SetupRequest();
+        (*m)->Request(server_->c_server(), server_cq_->cq());
+      }
+
+      Initialize();  // ThreadManager's Initialize()
+    }
+  }
+
+ private:
+  Server* server_;
+  grpc::CompletionQueue* server_cq_;
+  int cq_timeout_msec_;
+  std::vector<std::unique_ptr<SyncRequest>> sync_requests_;
+  std::unique_ptr<grpc::internal::RpcServiceMethod> unknown_method_;
+  std::shared_ptr<Server::GlobalCallbacks> global_callbacks_;
+};
+
+static grpc::internal::GrpcLibraryInitializer g_gli_initializer;
+Server::Server(
+    int max_receive_message_size, grpc::ChannelArguments* args,
+    std::shared_ptr<std::vector<std::unique_ptr<grpc::ServerCompletionQueue>>>
+        sync_server_cqs,
+    int min_pollers, int max_pollers, int sync_cq_timeout_msec,
+    grpc_resource_quota* server_rq,
+    std::vector<
+        std::unique_ptr<grpc::experimental::ServerInterceptorFactoryInterface>>
+        interceptor_creators)
+    : interceptor_creators_(std::move(interceptor_creators)),
+      max_receive_message_size_(max_receive_message_size),
+      sync_server_cqs_(std::move(sync_server_cqs)),
+      started_(false),
+      shutdown_(false),
+      shutdown_notified_(false),
+      server_(nullptr),
+      server_initializer_(new grpc_impl::ServerInitializer(this)),
+      health_check_service_disabled_(false) {
+  g_gli_initializer.summon();
+  gpr_once_init(&grpc::g_once_init_callbacks, grpc::InitGlobalCallbacks);
+  global_callbacks_ = grpc::g_callbacks;
+  global_callbacks_->UpdateArguments(args);
+
+  if (sync_server_cqs_ != nullptr) {
+    bool default_rq_created = false;
+    if (server_rq == nullptr) {
+      server_rq = grpc_resource_quota_create("SyncServer-default-rq");
+      grpc_resource_quota_set_max_threads(server_rq,
+                                          DEFAULT_MAX_SYNC_SERVER_THREADS);
+      default_rq_created = true;
+    }
+
+    for (const auto& it : *sync_server_cqs_) {
+      sync_req_mgrs_.emplace_back(new SyncRequestThreadManager(
+          this, it.get(), global_callbacks_, server_rq, min_pollers,
+          max_pollers, sync_cq_timeout_msec));
+    }
+
+    if (default_rq_created) {
+      grpc_resource_quota_unref(server_rq);
+    }
+  }
+
+  grpc_channel_args channel_args;
+  args->SetChannelArgs(&channel_args);
+
+  for (size_t i = 0; i < channel_args.num_args; i++) {
+    if (0 == strcmp(channel_args.args[i].key,
+                    grpc::kHealthCheckServiceInterfaceArg)) {
+      if (channel_args.args[i].value.pointer.p == nullptr) {
+        health_check_service_disabled_ = true;
+      } else {
+        health_check_service_.reset(
+            static_cast<grpc::HealthCheckServiceInterface*>(
+                channel_args.args[i].value.pointer.p));
+      }
+      break;
+    }
+  }
+
+  server_ = grpc_server_create(&channel_args, nullptr);
+}
+
+Server::~Server() {
+  {
+    grpc::internal::ReleasableMutexLock lock(&mu_);
+    if (callback_cq_ != nullptr) {
+      callback_cq_->Shutdown();
+    }
+    if (started_ && !shutdown_) {
+      lock.Unlock();
+      Shutdown();
+    } else if (!started_) {
+      // Shutdown the completion queues
+      for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
+        (*it)->Shutdown();
+      }
+    }
+  }
+
+  grpc_server_destroy(server_);
+  for (auto& per_method_count : callback_unmatched_reqs_count_) {
+    // There should be no more unmatched callbacks for any method
+    // as each request is failed by Shutdown. Check that this actually
+    // happened
+    GPR_ASSERT(static_cast<int>(gpr_atm_no_barrier_load(&per_method_count)) ==
+               0);
+  }
+}
+
+void Server::SetGlobalCallbacks(GlobalCallbacks* callbacks) {
+  GPR_ASSERT(!grpc::g_callbacks);
+  GPR_ASSERT(callbacks);
+  grpc::g_callbacks.reset(callbacks);
+}
+
+grpc_server* Server::c_server() { return server_; }
+
+std::shared_ptr<grpc::Channel> Server::InProcessChannel(
+    const grpc::ChannelArguments& args) {
+  grpc_channel_args channel_args = args.c_channel_args();
+  return grpc::CreateChannelInternal(
+      "inproc", grpc_inproc_channel_create(server_, &channel_args, nullptr),
+      std::vector<std::unique_ptr<
+          grpc::experimental::ClientInterceptorFactoryInterface>>());
+}
+
+std::shared_ptr<grpc::Channel>
+Server::experimental_type::InProcessChannelWithInterceptors(
+    const grpc::ChannelArguments& args,
+    std::vector<
+        std::unique_ptr<grpc::experimental::ClientInterceptorFactoryInterface>>
+        interceptor_creators) {
+  grpc_channel_args channel_args = args.c_channel_args();
+  return grpc::CreateChannelInternal(
+      "inproc",
+      grpc_inproc_channel_create(server_->server_, &channel_args, nullptr),
+      std::move(interceptor_creators));
+}
+
+static grpc_server_register_method_payload_handling PayloadHandlingForMethod(
+    grpc::internal::RpcServiceMethod* method) {
+  switch (method->method_type()) {
+    case grpc::internal::RpcMethod::NORMAL_RPC:
+    case grpc::internal::RpcMethod::SERVER_STREAMING:
+      return GRPC_SRM_PAYLOAD_READ_INITIAL_BYTE_BUFFER;
+    case grpc::internal::RpcMethod::CLIENT_STREAMING:
+    case grpc::internal::RpcMethod::BIDI_STREAMING:
+      return GRPC_SRM_PAYLOAD_NONE;
+  }
+  GPR_UNREACHABLE_CODE(return GRPC_SRM_PAYLOAD_NONE;);
+}
+
+bool Server::RegisterService(const grpc::string* host, grpc::Service* service) {
+  bool has_async_methods = service->has_async_methods();
+  if (has_async_methods) {
+    GPR_ASSERT(service->server_ == nullptr &&
+               "Can only register an asynchronous service against one server.");
+    service->server_ = this;
+  }
+
+  const char* method_name = nullptr;
+
+  for (auto it = service->methods_.begin(); it != service->methods_.end();
+       ++it) {
+    if (it->get() == nullptr) {  // Handled by generic service if any.
+      continue;
+    }
+
+    grpc::internal::RpcServiceMethod* method = it->get();
+    void* method_registration_tag = grpc_server_register_method(
+        server_, method->name(), host ? host->c_str() : nullptr,
+        PayloadHandlingForMethod(method), 0);
+    if (method_registration_tag == nullptr) {
+      gpr_log(GPR_DEBUG, "Attempt to register %s multiple times",
+              method->name());
+      return false;
+    }
+
+    if (method->handler() == nullptr) {  // Async method without handler
+      method->set_server_tag(method_registration_tag);
+    } else if (method->api_type() ==
+               grpc::internal::RpcServiceMethod::ApiType::SYNC) {
+      for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
+        (*it)->AddSyncMethod(method, method_registration_tag);
+      }
+    } else {
+      // a callback method. Register at least some callback requests
+      callback_unmatched_reqs_count_.push_back(0);
+      auto method_index = callback_unmatched_reqs_count_.size() - 1;
+      // TODO(vjpai): Register these dynamically based on need
+      for (int i = 0; i < DEFAULT_CALLBACK_REQS_PER_METHOD; i++) {
+        callback_reqs_to_start_.push_back(
+            new CallbackRequest<grpc::ServerContext>(this, method_index, method,
+                                                     method_registration_tag));
+      }
+      // Enqueue it so that it will be Request'ed later after all request
+      // matchers are created at core server startup
+    }
+
+    method_name = method->name();
+  }
+
+  // Parse service name.
+  if (method_name != nullptr) {
+    std::stringstream ss(method_name);
+    grpc::string service_name;
+    if (std::getline(ss, service_name, '/') &&
+        std::getline(ss, service_name, '/')) {
+      services_.push_back(service_name);
+    }
+  }
+  return true;
+}
+
+void Server::RegisterAsyncGenericService(grpc::AsyncGenericService* service) {
+  GPR_ASSERT(service->server_ == nullptr &&
+             "Can only register an async generic service against one server.");
+  service->server_ = this;
+  has_async_generic_service_ = true;
+}
+
+void Server::RegisterCallbackGenericService(
+    grpc::experimental::CallbackGenericService* service) {
+  GPR_ASSERT(
+      service->server_ == nullptr &&
+      "Can only register a callback generic service against one server.");
+  service->server_ = this;
+  has_callback_generic_service_ = true;
+  generic_handler_.reset(service->Handler());
+
+  callback_unmatched_reqs_count_.push_back(0);
+  auto method_index = callback_unmatched_reqs_count_.size() - 1;
+  // TODO(vjpai): Register these dynamically based on need
+  for (int i = 0; i < DEFAULT_CALLBACK_REQS_PER_METHOD; i++) {
+    callback_reqs_to_start_.push_back(
+        new CallbackRequest<grpc::GenericServerContext>(this, method_index,
+                                                        nullptr, nullptr));
+  }
+}
+
+int Server::AddListeningPort(const grpc::string& addr,
+                             grpc::ServerCredentials* creds) {
+  GPR_ASSERT(!started_);
+  int port = creds->AddPortToServer(addr, server_);
+  global_callbacks_->AddPort(this, addr, creds, port);
+  return port;
+}
+
+void Server::Start(grpc::ServerCompletionQueue** cqs, size_t num_cqs) {
+  GPR_ASSERT(!started_);
+  global_callbacks_->PreServerStart(this);
+  started_ = true;
+
+  // Only create default health check service when user did not provide an
+  // explicit one.
+  grpc::ServerCompletionQueue* health_check_cq = nullptr;
+  grpc::DefaultHealthCheckService::HealthCheckServiceImpl*
+      default_health_check_service_impl = nullptr;
+  if (health_check_service_ == nullptr && !health_check_service_disabled_ &&
+      grpc::DefaultHealthCheckServiceEnabled()) {
+    auto* default_hc_service = new grpc::DefaultHealthCheckService;
+    health_check_service_.reset(default_hc_service);
+    // We create a non-polling CQ to avoid impacting application
+    // performance.  This ensures that we don't introduce thread hops
+    // for application requests that wind up on this CQ, which is polled
+    // in its own thread.
+    health_check_cq = new grpc::ServerCompletionQueue(
+        GRPC_CQ_NEXT, GRPC_CQ_NON_POLLING, nullptr);
+    grpc_server_register_completion_queue(server_, health_check_cq->cq(),
+                                          nullptr);
+    default_health_check_service_impl =
+        default_hc_service->GetHealthCheckService(
+            std::unique_ptr<grpc::ServerCompletionQueue>(health_check_cq));
+    RegisterService(nullptr, default_health_check_service_impl);
+  }
+
+  // If this server uses callback methods, then create a callback generic
+  // service to handle any unimplemented methods using the default reactor
+  // creator
+  if (!callback_reqs_to_start_.empty() && !has_callback_generic_service_) {
+    unimplemented_service_.reset(
+        new grpc::experimental::CallbackGenericService);
+    RegisterCallbackGenericService(unimplemented_service_.get());
+  }
+
+  grpc_server_start(server_);
+
+  if (!has_async_generic_service_ && !has_callback_generic_service_) {
+    for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
+      (*it)->AddUnknownSyncMethod();
+    }
+
+    for (size_t i = 0; i < num_cqs; i++) {
+      if (cqs[i]->IsFrequentlyPolled()) {
+        new UnimplementedAsyncRequest(this, cqs[i]);
+      }
+    }
+    if (health_check_cq != nullptr) {
+      new UnimplementedAsyncRequest(this, health_check_cq);
+    }
+  }
+
+  // If this server has any support for synchronous methods (has any sync
+  // server CQs), make sure that we have a ResourceExhausted handler
+  // to deal with the case of thread exhaustion
+  if (sync_server_cqs_ != nullptr && !sync_server_cqs_->empty()) {
+    resource_exhausted_handler_.reset(
+        new grpc::internal::ResourceExhaustedHandler);
+  }
+
+  for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
+    (*it)->Start();
+  }
+
+  for (auto* cbreq : callback_reqs_to_start_) {
+    GPR_ASSERT(cbreq->Request());
+  }
+  callback_reqs_to_start_.clear();
+
+  if (default_health_check_service_impl != nullptr) {
+    default_health_check_service_impl->StartServingThread();
+  }
+}
+
+void Server::ShutdownInternal(gpr_timespec deadline) {
+  grpc::internal::MutexLock lock(&mu_);
+  if (shutdown_) {
+    return;
+  }
+
+  shutdown_ = true;
+
+  /// The completion queue to use for server shutdown completion notification
+  grpc::CompletionQueue shutdown_cq;
+  grpc::ShutdownTag shutdown_tag;  // Dummy shutdown tag
+  grpc_server_shutdown_and_notify(server_, shutdown_cq.cq(), &shutdown_tag);
+
+  shutdown_cq.Shutdown();
+
+  void* tag;
+  bool ok;
+  grpc::CompletionQueue::NextStatus status =
+      shutdown_cq.AsyncNext(&tag, &ok, deadline);
+
+  // If this timed out, it means we are done with the grace period for a clean
+  // shutdown. We should force a shutdown now by cancelling all inflight calls
+  if (status == grpc::CompletionQueue::NextStatus::TIMEOUT) {
+    grpc_server_cancel_all_calls(server_);
+  }
+  // Else in case of SHUTDOWN or GOT_EVENT, it means that the server has
+  // successfully shutdown
+
+  // Shutdown all ThreadManagers. This will try to gracefully stop all the
+  // threads in the ThreadManagers (once they process any inflight requests)
+  for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
+    (*it)->Shutdown();  // ThreadManager's Shutdown()
+  }
+
+  // Wait for threads in all ThreadManagers to terminate
+  for (auto it = sync_req_mgrs_.begin(); it != sync_req_mgrs_.end(); it++) {
+    (*it)->Wait();
+  }
+
+  // Wait for all outstanding callback requests to complete
+  // (whether waiting for a match or already active).
+  // We know that no new requests will be created after this point
+  // because they are only created at server startup time or when
+  // we have a successful match on a request. During the shutdown phase,
+  // requests that have not yet matched will be failed rather than
+  // allowed to succeed, which will cause the server to delete the
+  // request and decrement the count. Possibly a request will match before
+  // the shutdown but then find that shutdown has already started by the
+  // time it tries to register a new request. In that case, the registration
+  // will report a failure, indicating a shutdown and again we won't end
+  // up incrementing the counter.
+  {
+    grpc::internal::MutexLock cblock(&callback_reqs_mu_);
+    callback_reqs_done_cv_.WaitUntil(
+        &callback_reqs_mu_, [this] { return callback_reqs_outstanding_ == 0; });
+  }
+
+  // Drain the shutdown queue (if the previous call to AsyncNext() timed out
+  // and we didn't remove the tag from the queue yet)
+  while (shutdown_cq.Next(&tag, &ok)) {
+    // Nothing to be done here. Just ignore ok and tag values
+  }
+
+  shutdown_notified_ = true;
+  shutdown_cv_.Broadcast();
+}
+
+void Server::Wait() {
+  grpc::internal::MutexLock lock(&mu_);
+  while (started_ && !shutdown_notified_) {
+    shutdown_cv_.Wait(&mu_);
+  }
+}
+
+void Server::PerformOpsOnCall(grpc::internal::CallOpSetInterface* ops,
+                              grpc::internal::Call* call) {
+  ops->FillOps(call);
+}
+
+bool Server::UnimplementedAsyncRequest::FinalizeResult(void** tag,
+                                                       bool* status) {
+  if (GenericAsyncRequest::FinalizeResult(tag, status)) {
+    // We either had no interceptors run or we are done intercepting
+    if (*status) {
+      new UnimplementedAsyncRequest(server_, cq_);
+      new UnimplementedAsyncResponse(this);
+    } else {
+      delete this;
+    }
+  } else {
+    // The tag was swallowed due to interception. We will see it again.
+  }
+  return false;
+}
+
+Server::UnimplementedAsyncResponse::UnimplementedAsyncResponse(
+    UnimplementedAsyncRequest* request)
+    : request_(request) {
+  grpc::Status status(grpc::StatusCode::UNIMPLEMENTED, "");
+  grpc::internal::UnknownMethodHandler::FillOps(request_->context(), this);
+  request_->stream()->call_.PerformOps(this);
+}
+
+grpc::ServerInitializer* Server::initializer() {
+  return server_initializer_.get();
+}
+
+grpc::CompletionQueue* Server::CallbackCQ() {
   // TODO(vjpai): Consider using a single global CQ for the default CQ
   // if there is no explicit per-server CQ registered
-  std::lock_guard<std::mutex> l(mu_);
+  grpc::internal::MutexLock l(&mu_);
   if (callback_cq_ == nullptr) {
-    auto* shutdown_callback = new ShutdownCallback;
-    callback_cq_ = new CompletionQueue(grpc_completion_queue_attributes{
+    auto* shutdown_callback = new grpc::ShutdownCallback;
+    callback_cq_ = new grpc::CompletionQueue(grpc_completion_queue_attributes{
         GRPC_CQ_CURRENT_VERSION, GRPC_CQ_CALLBACK, GRPC_CQ_DEFAULT_POLLING,
         shutdown_callback});
 
@@ -1335,4 +1359,4 @@
   return callback_cq_;
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/server/server_context.cc b/src/cpp/server/server_context.cc
index 73fd6a6..eced89d 100644
--- a/src/cpp/server/server_context.cc
+++ b/src/cpp/server/server_context.cc
@@ -33,6 +33,7 @@
 #include <grpcpp/support/time.h>
 
 #include "src/core/lib/gprpp/ref_counted.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/surface/call.h"
 
 namespace grpc {
@@ -96,7 +97,7 @@
   }
 
   void SetCancelCallback(std::function<void()> callback) {
-    std::lock_guard<std::mutex> lock(mu_);
+    grpc_core::MutexLock lock(&mu_);
 
     if (finalized_ && (cancelled_ != 0)) {
       callback();
@@ -107,7 +108,7 @@
   }
 
   void ClearCancelCallback() {
-    std::lock_guard<std::mutex> g(mu_);
+    grpc_core::MutexLock g(&mu_);
     cancel_callback_ = nullptr;
   }
 
@@ -144,7 +145,7 @@
 
  private:
   bool CheckCancelledNoPluck() {
-    std::lock_guard<std::mutex> g(mu_);
+    grpc_core::MutexLock lock(&mu_);
     return finalized_ ? (cancelled_ != 0) : false;
   }
 
@@ -154,7 +155,7 @@
   void* tag_;
   void* core_cq_tag_;
   grpc_core::RefCount refs_;
-  std::mutex mu_;
+  grpc_core::Mutex mu_;
   bool finalized_;
   int cancelled_;  // This is an int (not bool) because it is passed to core
   std::function<void()> cancel_callback_;
@@ -186,7 +187,7 @@
 
 bool ServerContext::CompletionOp::FinalizeResult(void** tag, bool* status) {
   bool ret = false;
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::ReleasableMutexLock lock(&mu_);
   if (done_intercepting_) {
     /* We are done intercepting. */
     if (has_tag_) {
@@ -209,19 +210,21 @@
   bool call_cancel = (cancelled_ != 0);
 
   // If it's a unary cancel callback, call it under the lock so that it doesn't
-  // race with ClearCancelCallback
+  // race with ClearCancelCallback. Although we don't normally call callbacks
+  // under a lock, this is a special case since the user needs a guarantee that
+  // the callback won't issue or run after ClearCancelCallback has returned.
+  // This requirement imposes certain restrictions on the callback, documented
+  // in the API comments of SetCancelCallback.
   if (cancel_callback_) {
     cancel_callback_();
   }
 
-  // Release the lock since we are going to be calling a callback and
-  // interceptors now
-  lock.unlock();
+  // Release the lock since we may call a callback and interceptors now.
+  lock.Unlock();
 
   if (call_cancel && reactor_ != nullptr) {
-    reactor_->OnCancel();
+    reactor_->MaybeCallOnCancel();
   }
-
   /* Add interception point and run through interceptors */
   interceptor_methods_.AddInterceptionHookPoint(
       experimental::InterceptionHookPoints::POST_RECV_CLOSE);
diff --git a/src/cpp/server/server_credentials.cc b/src/cpp/server/server_credentials.cc
index c3b3a8b..8b85264 100644
--- a/src/cpp/server/server_credentials.cc
+++ b/src/cpp/server/server_credentials.cc
@@ -16,10 +16,10 @@
  *
  */
 
-#include <grpcpp/security/server_credentials.h>
+#include <grpcpp/security/server_credentials_impl.h>
 
-namespace grpc {
+namespace grpc_impl {
 
 ServerCredentials::~ServerCredentials() {}
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/cpp/thread_manager/thread_manager.cc b/src/cpp/thread_manager/thread_manager.cc
index 3e8606a..2b65352 100644
--- a/src/cpp/thread_manager/thread_manager.cc
+++ b/src/cpp/thread_manager/thread_manager.cc
@@ -62,7 +62,7 @@
 
 ThreadManager::~ThreadManager() {
   {
-    std::lock_guard<std::mutex> lock(mu_);
+    grpc_core::MutexLock lock(&mu_);
     GPR_ASSERT(num_threads_ == 0);
   }
 
@@ -72,38 +72,38 @@
 }
 
 void ThreadManager::Wait() {
-  std::unique_lock<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   while (num_threads_ != 0) {
-    shutdown_cv_.wait(lock);
+    shutdown_cv_.Wait(&mu_);
   }
 }
 
 void ThreadManager::Shutdown() {
-  std::lock_guard<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   shutdown_ = true;
 }
 
 bool ThreadManager::IsShutdown() {
-  std::lock_guard<std::mutex> lock(mu_);
+  grpc_core::MutexLock lock(&mu_);
   return shutdown_;
 }
 
 int ThreadManager::GetMaxActiveThreadsSoFar() {
-  std::lock_guard<std::mutex> list_lock(list_mu_);
+  grpc_core::MutexLock list_lock(&list_mu_);
   return max_active_threads_sofar_;
 }
 
 void ThreadManager::MarkAsCompleted(WorkerThread* thd) {
   {
-    std::lock_guard<std::mutex> list_lock(list_mu_);
+    grpc_core::MutexLock list_lock(&list_mu_);
     completed_threads_.push_back(thd);
   }
 
   {
-    std::lock_guard<std::mutex> lock(mu_);
+    grpc_core::MutexLock lock(&mu_);
     num_threads_--;
     if (num_threads_ == 0) {
-      shutdown_cv_.notify_one();
+      shutdown_cv_.Signal();
     }
   }
 
@@ -116,7 +116,7 @@
   {
     // swap out the completed threads list: allows other threads to clean up
     // more quickly
-    std::unique_lock<std::mutex> lock(list_mu_);
+    grpc_core::MutexLock lock(&list_mu_);
     completed_threads.swap(completed_threads_);
   }
   for (auto thd : completed_threads) delete thd;
@@ -132,7 +132,7 @@
   }
 
   {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc_core::MutexLock lock(&mu_);
     num_pollers_ = min_pollers_;
     num_threads_ = min_pollers_;
     max_active_threads_sofar_ = min_pollers_;
@@ -149,7 +149,7 @@
     bool ok;
     WorkStatus work_status = PollForWork(&tag, &ok);
 
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc_core::ReleasableMutexLock lock(&mu_);
     // Reduce the number of pollers by 1 and check what happened with the poll
     num_pollers_--;
     bool done = false;
@@ -176,30 +176,30 @@
               max_active_threads_sofar_ = num_threads_;
             }
             // Drop lock before spawning thread to avoid contention
-            lock.unlock();
+            lock.Unlock();
             new WorkerThread(this);
           } else if (num_pollers_ > 0) {
             // There is still at least some thread polling, so we can go on
             // even though we are below the number of pollers that we would
             // like to have (min_pollers_)
-            lock.unlock();
+            lock.Unlock();
           } else {
             // There are no pollers to spare and we couldn't allocate
             // a new thread, so resources are exhausted!
-            lock.unlock();
+            lock.Unlock();
             resource_exhausted = true;
           }
         } else {
           // There are a sufficient number of pollers available so we can do
           // the work and continue polling with our existing poller threads
-          lock.unlock();
+          lock.Unlock();
         }
         // Lock is always released at this point - do the application work
         // or return resource exhausted if there is new work but we couldn't
         // get a thread in which to do it.
         DoWork(tag, ok, !resource_exhausted);
         // Take the lock again to check post conditions
-        lock.lock();
+        lock.Lock();
         // If we're shutdown, we should finish at this point.
         if (shutdown_) done = true;
         break;
diff --git a/src/cpp/thread_manager/thread_manager.h b/src/cpp/thread_manager/thread_manager.h
index 6f0bd17..2fbf309 100644
--- a/src/cpp/thread_manager/thread_manager.h
+++ b/src/cpp/thread_manager/thread_manager.h
@@ -26,6 +26,7 @@
 
 #include <grpcpp/support/config.h>
 
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/gprpp/thd.h"
 #include "src/core/lib/iomgr/resource_quota.h"
 
@@ -140,10 +141,10 @@
 
   // Protects shutdown_, num_pollers_, num_threads_ and
   // max_active_threads_sofar_
-  std::mutex mu_;
+  grpc_core::Mutex mu_;
 
   bool shutdown_;
-  std::condition_variable shutdown_cv_;
+  grpc_core::CondVar shutdown_cv_;
 
   // The resource user object to use when requesting quota to create threads
   //
@@ -169,7 +170,7 @@
   // ever set so far
   int max_active_threads_sofar_;
 
-  std::mutex list_mu_;
+  grpc_core::Mutex list_mu_;
   std::list<WorkerThread*> completed_threads_;
 };
 
diff --git a/src/cpp/util/error_details.cc b/src/cpp/util/error_details.cc
index 42c887a..a1aafcb 100644
--- a/src/cpp/util/error_details.cc
+++ b/src/cpp/util/error_details.cc
@@ -20,29 +20,31 @@
 
 #include "src/proto/grpc/status/status.pb.h"
 
-namespace grpc {
+namespace grpc_impl {
 
-Status ExtractErrorDetails(const Status& from, ::google::rpc::Status* to) {
+grpc::Status ExtractErrorDetails(const grpc::Status& from,
+                                 ::google::rpc::Status* to) {
   if (to == nullptr) {
-    return Status(StatusCode::FAILED_PRECONDITION, "");
+    return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "");
   }
   if (!to->ParseFromString(from.error_details())) {
-    return Status(StatusCode::INVALID_ARGUMENT, "");
+    return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "");
   }
-  return Status::OK;
+  return grpc::Status::OK;
 }
 
-Status SetErrorDetails(const ::google::rpc::Status& from, Status* to) {
+grpc::Status SetErrorDetails(const ::google::rpc::Status& from,
+                             grpc::Status* to) {
   if (to == nullptr) {
-    return Status(StatusCode::FAILED_PRECONDITION, "");
+    return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "");
   }
-  StatusCode code = StatusCode::UNKNOWN;
-  if (from.code() >= StatusCode::OK &&
-      from.code() <= StatusCode::UNAUTHENTICATED) {
-    code = static_cast<StatusCode>(from.code());
+  grpc::StatusCode code = grpc::StatusCode::UNKNOWN;
+  if (from.code() >= grpc::StatusCode::OK &&
+      from.code() <= grpc::StatusCode::UNAUTHENTICATED) {
+    code = static_cast<grpc::StatusCode>(from.code());
   }
-  *to = Status(code, from.message(), from.SerializeAsString());
-  return Status::OK;
+  *to = grpc::Status(code, from.message(), from.SerializeAsString());
+  return grpc::Status::OK;
 }
 
-}  // namespace grpc
+}  // namespace grpc_impl
diff --git a/src/csharp/Grpc.Core/AsyncAuthInterceptor.cs b/src/csharp/Grpc.Core.Api/AsyncAuthInterceptor.cs
similarity index 100%
rename from src/csharp/Grpc.Core/AsyncAuthInterceptor.cs
rename to src/csharp/Grpc.Core.Api/AsyncAuthInterceptor.cs
diff --git a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs b/src/csharp/Grpc.Core.Api/AsyncClientStreamingCall.cs
similarity index 100%
rename from src/csharp/Grpc.Core/AsyncClientStreamingCall.cs
rename to src/csharp/Grpc.Core.Api/AsyncClientStreamingCall.cs
diff --git a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs b/src/csharp/Grpc.Core.Api/AsyncDuplexStreamingCall.cs
similarity index 100%
rename from src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs
rename to src/csharp/Grpc.Core.Api/AsyncDuplexStreamingCall.cs
diff --git a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs b/src/csharp/Grpc.Core.Api/AsyncServerStreamingCall.cs
similarity index 100%
rename from src/csharp/Grpc.Core/AsyncServerStreamingCall.cs
rename to src/csharp/Grpc.Core.Api/AsyncServerStreamingCall.cs
diff --git a/src/csharp/Grpc.Core/AsyncUnaryCall.cs b/src/csharp/Grpc.Core.Api/AsyncUnaryCall.cs
similarity index 100%
rename from src/csharp/Grpc.Core/AsyncUnaryCall.cs
rename to src/csharp/Grpc.Core.Api/AsyncUnaryCall.cs
diff --git a/src/csharp/Grpc.Core.Api/BindServiceMethodAttribute.cs b/src/csharp/Grpc.Core.Api/BindServiceMethodAttribute.cs
new file mode 100644
index 0000000..2ee0abe
--- /dev/null
+++ b/src/csharp/Grpc.Core.Api/BindServiceMethodAttribute.cs
@@ -0,0 +1,53 @@
+#region Copyright notice and license
+// Copyright 2019 The 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.
+#endregion
+
+using System;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Specifies the location of the service bind method for a gRPC service.
+    /// The bind method is typically generated code and is used to register a service's
+    /// methods with the server on startup.
+    /// 
+    /// The bind method signature takes a <see cref="ServiceBinderBase"/> and an optional
+    /// instance of the service base class, e.g. <c>static void BindService(ServiceBinderBase, GreeterService)</c>.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+    public class BindServiceMethodAttribute : Attribute
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BindServiceMethodAttribute"/> class.
+        /// </summary>
+        /// <param name="bindType">The type the service bind method is defined on.</param>
+        /// <param name="bindMethodName">The name of the service bind method.</param>
+        public BindServiceMethodAttribute(Type bindType, string bindMethodName)
+        {
+            BindType = bindType;
+            BindMethodName = bindMethodName;
+        }
+
+        /// <summary>
+        /// Gets the type the service bind method is defined on.
+        /// </summary>
+        public Type BindType { get; }
+
+        /// <summary>
+        /// Gets the name of the service bind method.
+        /// </summary>
+        public string BindMethodName { get; }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Api/CallCredentials.cs b/src/csharp/Grpc.Core.Api/CallCredentials.cs
new file mode 100644
index 0000000..6344a88
--- /dev/null
+++ b/src/csharp/Grpc.Core.Api/CallCredentials.cs
@@ -0,0 +1,90 @@
+#region Copyright notice and license
+
+// 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.
+
+#endregion
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Client-side call credentials. Provide authorization with per-call granularity.
+    /// </summary>
+    public abstract class CallCredentials
+    {
+        /// <summary>
+        /// Composes multiple multiple <c>CallCredentials</c> objects into
+        /// a single <c>CallCredentials</c> object.
+        /// </summary>
+        /// <param name="credentials">credentials to compose</param>
+        /// <returns>The new <c>CompositeCallCredentials</c></returns>
+        public static CallCredentials Compose(params CallCredentials[] credentials)
+        {
+            return new CompositeCallCredentials(credentials);
+        }
+
+        /// <summary>
+        /// Creates a new instance of <c>CallCredentials</c> class from an
+        /// interceptor that can attach metadata to outgoing calls.
+        /// </summary>
+        /// <param name="interceptor">authentication interceptor</param>
+        public static CallCredentials FromInterceptor(AsyncAuthInterceptor interceptor)
+        {
+            return new AsyncAuthInterceptorCredentials(interceptor);
+        }
+
+        /// <summary>
+        /// Populates this call credential instances.
+        /// You never need to invoke this, part of internal implementation.
+        /// </summary>
+        public abstract void InternalPopulateConfiguration(CallCredentialsConfiguratorBase configurator, object state);
+
+        private class CompositeCallCredentials : CallCredentials
+        {
+            readonly IReadOnlyList<CallCredentials> credentials;
+
+            public CompositeCallCredentials(CallCredentials[] credentials)
+            {
+                GrpcPreconditions.CheckArgument(credentials.Length >= 2, "Composite credentials object can only be created from 2 or more credentials.");
+                this.credentials = new List<CallCredentials>(credentials).AsReadOnly();
+            }
+
+            public override void InternalPopulateConfiguration(CallCredentialsConfiguratorBase configurator, object state)
+            {
+                configurator.SetCompositeCredentials(state, credentials);
+            }
+        }
+
+        private class AsyncAuthInterceptorCredentials : CallCredentials
+        {
+            readonly AsyncAuthInterceptor interceptor;
+
+            public AsyncAuthInterceptorCredentials(AsyncAuthInterceptor interceptor)
+            {
+                this.interceptor = GrpcPreconditions.CheckNotNull(interceptor);
+            }
+
+            public override void InternalPopulateConfiguration(CallCredentialsConfiguratorBase configurator, object state)
+            {
+                configurator.SetAsyncAuthInterceptorCredentials(state, interceptor);
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Api/CallCredentialsConfiguratorBase.cs b/src/csharp/Grpc.Core.Api/CallCredentialsConfiguratorBase.cs
new file mode 100644
index 0000000..51f8d72
--- /dev/null
+++ b/src/csharp/Grpc.Core.Api/CallCredentialsConfiguratorBase.cs
@@ -0,0 +1,39 @@
+#region Copyright notice and license
+
+// Copyright 2019 The 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.
+
+#endregion
+
+using System.Collections.Generic;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Base class for objects that can consume configuration from <c>CallCredentials</c> objects.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// </summary>
+    public abstract class CallCredentialsConfiguratorBase
+    {
+        /// <summary>
+        /// Consumes configuration for composite call credentials.
+        /// </summary>
+        public abstract void SetCompositeCredentials(object state, IReadOnlyList<CallCredentials> credentials);
+
+        /// <summary>
+        /// Consumes configuration for call credentials created from <c>AsyncAuthInterceptor</c>
+        /// </summary>
+        public abstract void SetAsyncAuthInterceptorCredentials(object state, AsyncAuthInterceptor interceptor);
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/CallFlags.cs b/src/csharp/Grpc.Core.Api/CallFlags.cs
similarity index 100%
rename from src/csharp/Grpc.Core/Internal/CallFlags.cs
rename to src/csharp/Grpc.Core.Api/CallFlags.cs
diff --git a/src/csharp/Grpc.Core/CallInvoker.cs b/src/csharp/Grpc.Core.Api/CallInvoker.cs
similarity index 97%
rename from src/csharp/Grpc.Core/CallInvoker.cs
rename to src/csharp/Grpc.Core.Api/CallInvoker.cs
index 09fbf0b..8a84a52 100644
--- a/src/csharp/Grpc.Core/CallInvoker.cs
+++ b/src/csharp/Grpc.Core.Api/CallInvoker.cs
@@ -17,14 +17,12 @@
 #endregion
 
 using System.Threading.Tasks;
-using Grpc.Core.Internal;
 
 namespace Grpc.Core
 {
     /// <summary>
     /// Abstraction of client-side RPC invocation.
     /// </summary>
-    /// <seealso cref="Calls"/>
     public abstract class CallInvoker
     {
         /// <summary>
diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core.Api/CallOptions.cs
similarity index 84%
rename from src/csharp/Grpc.Core/CallOptions.cs
rename to src/csharp/Grpc.Core.Api/CallOptions.cs
index a92caae..c0b732a 100644
--- a/src/csharp/Grpc.Core/CallOptions.cs
+++ b/src/csharp/Grpc.Core.Api/CallOptions.cs
@@ -20,7 +20,6 @@
 using System.Threading;
 
 using Grpc.Core.Internal;
-using Grpc.Core.Utils;
 
 namespace Grpc.Core
 {
@@ -227,36 +226,5 @@
             newOptions.flags = flags;
             return newOptions;
         }
-
-        /// <summary>
-        /// Returns a new instance of <see cref="CallOptions"/> with 
-        /// all previously unset values set to their defaults and deadline and cancellation
-        /// token propagated when appropriate.
-        /// </summary>
-        internal CallOptions Normalize()
-        {
-            var newOptions = this;
-            // silently ignore the context propagation token if it wasn't produced by "us"
-            var propagationTokenImpl = propagationToken.AsImplOrNull();
-            if (propagationTokenImpl != null)
-            {
-                if (propagationTokenImpl.Options.IsPropagateDeadline)
-                {
-                    GrpcPreconditions.CheckArgument(!newOptions.deadline.HasValue,
-                        "Cannot propagate deadline from parent call. The deadline has already been set explicitly.");
-                    newOptions.deadline = propagationTokenImpl.ParentDeadline;
-                }
-                if (propagationTokenImpl.Options.IsPropagateCancellation)
-                {
-                    GrpcPreconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled,
-                        "Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value.");
-                    newOptions.cancellationToken = propagationTokenImpl.ParentCancellationToken;
-                }
-            }
-
-            newOptions.headers = newOptions.headers ?? Metadata.Empty;
-            newOptions.deadline = newOptions.deadline ?? DateTime.MaxValue;
-            return newOptions;
-        }
     }
 }
diff --git a/src/csharp/Grpc.Core.Api/DeserializationContext.cs b/src/csharp/Grpc.Core.Api/DeserializationContext.cs
index d69e0db..966bcfa 100644
--- a/src/csharp/Grpc.Core.Api/DeserializationContext.cs
+++ b/src/csharp/Grpc.Core.Api/DeserializationContext.cs
@@ -39,7 +39,7 @@
         /// Also, allocating a new buffer each time can put excessive pressure on GC, especially if
         /// the payload is more than 86700 bytes large (which means the newly allocated buffer will be placed in LOH,
         /// and LOH object can only be garbage collected via a full ("stop the world") GC run).
-        /// NOTE: Deserializers are expected not to call this method more than once per received message
+        /// NOTE: Deserializers are expected not to call this method (or other payload accessor methods) more than once per received message
         /// (as there is no practical reason for doing so) and <c>DeserializationContext</c> implementations are free to assume so.
         /// </summary>
         /// <returns>byte array containing the entire payload.</returns>
@@ -47,5 +47,22 @@
         {
             throw new NotImplementedException();
         }
+
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+        /// <summary>
+        /// Gets the entire payload as a ReadOnlySequence.
+        /// The ReadOnlySequence is only valid for the duration of the deserializer routine and the caller must not access it after the deserializer returns.
+        /// Using the read only sequence is the most efficient way to access the message payload. Where possible it allows directly
+        /// accessing the received payload without needing to perform any buffer copying or buffer allocations.
+        /// NOTE: This method is only available in the netstandard2.0 build of the library.
+        /// NOTE: Deserializers are expected not to call this method (or other payload accessor methods) more than once per received message
+        /// (as there is no practical reason for doing so) and <c>DeserializationContext</c> implementations are free to assume so.
+        /// </summary>
+        /// <returns>read only sequence containing the entire payload.</returns>
+        public virtual System.Buffers.ReadOnlySequence<byte> PayloadAsReadOnlySequence()
+        {
+            throw new NotImplementedException();
+        }
+#endif        
     }
 }
diff --git a/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj b/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj
index 6c29530..8a32bc7 100755
--- a/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj
+++ b/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj
@@ -19,12 +19,20 @@
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
 
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
+    <DefineConstants>$(DefineConstants);GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY</DefineConstants>
+  </PropertyGroup>
+
   <Import Project="..\Grpc.Core\SourceLink.csproj.include" />
 
   <ItemGroup>
     <PackageReference Include="System.Interactive.Async" Version="3.2.0" />
   </ItemGroup>
 
+  <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
+    <PackageReference Include="System.Memory" Version="4.5.2" />
+  </ItemGroup>
+
   <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
     <Reference Include="System" />
     <Reference Include="Microsoft.CSharp" />
diff --git a/src/csharp/Grpc.Core/IClientStreamWriter.cs b/src/csharp/Grpc.Core.Api/IClientStreamWriter.cs
similarity index 100%
rename from src/csharp/Grpc.Core/IClientStreamWriter.cs
rename to src/csharp/Grpc.Core.Api/IClientStreamWriter.cs
diff --git a/src/csharp/Grpc.Core/Interceptors/ClientInterceptorContext.cs b/src/csharp/Grpc.Core.Api/Interceptors/ClientInterceptorContext.cs
similarity index 100%
rename from src/csharp/Grpc.Core/Interceptors/ClientInterceptorContext.cs
rename to src/csharp/Grpc.Core.Api/Interceptors/ClientInterceptorContext.cs
diff --git a/src/csharp/Grpc.Core/Interceptors/Interceptor.cs b/src/csharp/Grpc.Core.Api/Interceptors/Interceptor.cs
similarity index 100%
rename from src/csharp/Grpc.Core/Interceptors/Interceptor.cs
rename to src/csharp/Grpc.Core.Api/Interceptors/Interceptor.cs
diff --git a/src/csharp/Grpc.Core.Api/StatusCode.cs b/src/csharp/Grpc.Core.Api/StatusCode.cs
index 57fe538..8493f37 100644
--- a/src/csharp/Grpc.Core.Api/StatusCode.cs
+++ b/src/csharp/Grpc.Core.Api/StatusCode.cs
@@ -114,7 +114,8 @@
         /// <summary>
         /// The service is currently unavailable.  This is a most likely a
         /// transient condition and may be corrected by retrying with
-        /// a backoff.
+        /// a backoff. Note that it is not always safe to retry
+        /// non-idempotent operations.
         /// </summary>
         Unavailable = 14,
 
diff --git a/src/csharp/Grpc.Core.Api/VersionInfo.cs b/src/csharp/Grpc.Core.Api/VersionInfo.cs
index 402b6c3..1b2602e 100644
--- a/src/csharp/Grpc.Core.Api/VersionInfo.cs
+++ b/src/csharp/Grpc.Core.Api/VersionInfo.cs
@@ -33,11 +33,11 @@
         /// <summary>
         /// Current <c>AssemblyFileVersion</c> of gRPC C# assemblies
         /// </summary>
-        public const string CurrentAssemblyFileVersion = "1.20.1.0";
+        public const string CurrentAssemblyFileVersion = "1.21.0.0";
 
         /// <summary>
         /// Current version of gRPC C#
         /// </summary>
-        public const string CurrentVersion = "1.20.1";
+        public const string CurrentVersion = "1.21.0-dev";
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/FakeCredentials.cs b/src/csharp/Grpc.Core.Tests/FakeCredentials.cs
index f23c9e9..59587b9 100644
--- a/src/csharp/Grpc.Core.Tests/FakeCredentials.cs
+++ b/src/csharp/Grpc.Core.Tests/FakeCredentials.cs
@@ -42,9 +42,9 @@
 
     internal class FakeCallCredentials : CallCredentials
     {
-        internal override CallCredentialsSafeHandle ToNativeCredentials()
+        public override void InternalPopulateConfiguration(CallCredentialsConfiguratorBase configurator, object state)
         {
-            return null;
+            // not invoking the configurator on purpose
         }
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index 23e5d7f..7fef2c7 100755
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -8,6 +8,10 @@
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
 
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
+    <DefineConstants>$(DefineConstants);GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY</DefineConstants>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj" />
   </ItemGroup>
diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
index 5c7d48f..fd22161 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
@@ -35,6 +35,7 @@
         Server server;
         FakeNativeCall fakeCall;
         AsyncCallServer<string, string> asyncCallServer;
+        FakeBufferReaderManager fakeBufferReaderManager;
 
         [SetUp]
         public void Init()
@@ -52,11 +53,13 @@
                 Marshallers.StringMarshaller.ContextualSerializer, Marshallers.StringMarshaller.ContextualDeserializer,
                 server);
             asyncCallServer.InitializeForTesting(fakeCall);
+            fakeBufferReaderManager = new FakeBufferReaderManager();
         }
 
         [TearDown]
         public void Cleanup()
         {
+            fakeBufferReaderManager.Dispose();
             server.ShutdownAsync().Wait();
         }
 
@@ -77,7 +80,7 @@
             var moveNextTask = requestStream.MoveNext();
 
             fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             Assert.IsFalse(moveNextTask.Result);
 
             AssertFinished(asyncCallServer, fakeCall, finishedTask);
@@ -107,7 +110,7 @@
             // if a read completion's success==false, the request stream will silently finish
             // and we rely on C core cancelling the call.
             var moveNextTask = requestStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, CreateNullResponse());
             Assert.IsFalse(moveNextTask.Result);
 
             fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
@@ -182,5 +185,10 @@
             Assert.IsTrue(finishedTask.IsCompleted);
             Assert.DoesNotThrow(() => finishedTask.Wait());
         }
+
+        IBufferReader CreateNullResponse()
+        {
+            return fakeBufferReaderManager.CreateNullPayloadBufferReader();
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
index 775849d..78c7f3a 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
@@ -21,6 +21,7 @@
 using System.Threading.Tasks;
 
 using Grpc.Core.Internal;
+using Grpc.Core.Utils;
 using NUnit.Framework;
 
 namespace Grpc.Core.Internal.Tests
@@ -33,6 +34,7 @@
         Channel channel;
         FakeNativeCall fakeCall;
         AsyncCall<string, string> asyncCall;
+        FakeBufferReaderManager fakeBufferReaderManager;
 
         [SetUp]
         public void Init()
@@ -43,12 +45,14 @@
 
             var callDetails = new CallInvocationDetails<string, string>(channel, "someMethod", null, Marshallers.StringMarshaller, Marshallers.StringMarshaller, new CallOptions());
             asyncCall = new AsyncCall<string, string>(callDetails, fakeCall);
+            fakeBufferReaderManager = new FakeBufferReaderManager();
         }
 
         [TearDown]
         public void Cleanup()
         {
             channel.ShutdownAsync().Wait();
+            fakeBufferReaderManager.Dispose();
         }
 
         [Test]
@@ -87,7 +91,7 @@
             var resultTask = asyncCall.UnaryCallAsync("request1");
             fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.InvalidArgument),
-                null,
+                CreateNullResponse(),
                 new Metadata());
 
             AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.InvalidArgument);
@@ -168,7 +172,7 @@
             var resultTask = asyncCall.ClientStreamingCallAsync();
             fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.InvalidArgument),
-                null,
+                CreateNullResponse(),
                 new Metadata());
 
             AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.InvalidArgument);
@@ -214,7 +218,7 @@
 
             fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Internal),
-                null,
+                CreateNullResponse(),
                 new Metadata());
 
             var ex = Assert.ThrowsAsync<RpcException>(async () => await writeTask);
@@ -233,7 +237,7 @@
 
             fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Internal),
-                null,
+                CreateNullResponse(),
                 new Metadata());
 
             fakeCall.SendCompletionCallback.OnSendCompletion(false);
@@ -259,7 +263,7 @@
 
             fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Internal),
-                null,
+                CreateNullResponse(),
                 new Metadata());
 
             var ex = Assert.ThrowsAsync<RpcException>(async () => await writeTask);
@@ -357,7 +361,7 @@
 
             fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Cancelled),
-                null,
+                CreateNullResponse(),
                 new Metadata());
 
             AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.Cancelled);
@@ -390,7 +394,7 @@
             fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(true, new Metadata());
             Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
 
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
@@ -405,7 +409,7 @@
 
             // try alternative order of completions
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
         }
@@ -417,7 +421,7 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
             var readTask = responseStream.MoveNext();
 
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, null);  // after a failed read, we rely on C core to deliver appropriate status code.
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, CreateNullResponse());  // after a failed read, we rely on C core to deliver appropriate status code.
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Internal));
 
             AssertStreamingResponseError(asyncCall, fakeCall, readTask, StatusCode.Internal);
@@ -441,7 +445,7 @@
 
             var readTask3 = responseStream.MoveNext();
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask3);
         }
@@ -479,7 +483,7 @@
             Assert.DoesNotThrowAsync(async () => await writeTask1);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
@@ -493,7 +497,7 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
@@ -511,7 +515,7 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
@@ -533,7 +537,7 @@
             Assert.IsFalse(writeTask.IsCompleted);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.PermissionDenied));
 
             var ex = Assert.ThrowsAsync<RpcException>(async () => await writeTask);
@@ -552,7 +556,7 @@
             var writeTask = requestStream.WriteAsync("request1");
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.PermissionDenied));
             fakeCall.SendCompletionCallback.OnSendCompletion(false);
 
@@ -576,7 +580,7 @@
             Assert.ThrowsAsync(typeof(TaskCanceledException), async () => await writeTask);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled));
 
             AssertStreamingResponseError(asyncCall, fakeCall, readTask, StatusCode.Cancelled);
@@ -597,7 +601,7 @@
             Assert.AreEqual("response1", responseStream.Current);
 
             var readTask2 = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled));
 
             AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled);
@@ -618,7 +622,7 @@
             Assert.AreEqual("response1", responseStream.Current);
 
             var readTask2 = responseStream.MoveNext();
-            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse());
             fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled));
 
             AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled);
@@ -638,9 +642,14 @@
             return new ClientSideStatus(new Status(statusCode, ""), new Metadata());
         }
 
-        byte[] CreateResponsePayload()
+        IBufferReader CreateResponsePayload()
         {
-            return Marshallers.StringMarshaller.Serializer("response1");
+            return fakeBufferReaderManager.CreateSingleSegmentBufferReader(Marshallers.StringMarshaller.Serializer("response1"));
+        }
+
+        IBufferReader CreateNullResponse()
+        {
+            return fakeBufferReaderManager.CreateNullPayloadBufferReader();
         }
 
         static void AssertUnaryResponseSuccess(AsyncCall<string, string> asyncCall, FakeNativeCall fakeCall, Task<string> resultTask)
diff --git a/src/csharp/Grpc.Core.Tests/Internal/CompletionQueueSafeHandleTest.cs b/src/csharp/Grpc.Core.Tests/Internal/CompletionQueueSafeHandleTest.cs
index 7e4e297..a0ea1cc 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/CompletionQueueSafeHandleTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/CompletionQueueSafeHandleTest.cs
@@ -47,7 +47,6 @@
             GrpcEnvironment.ReleaseAsync().Wait();
             Assert.AreEqual(CompletionQueueEvent.CompletionType.Shutdown, ev.type);
             Assert.AreNotEqual(IntPtr.Zero, ev.success);
-            Assert.AreEqual(IntPtr.Zero, ev.tag);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core.Tests/Internal/DefaultDeserializationContextTest.cs b/src/csharp/Grpc.Core.Tests/Internal/DefaultDeserializationContextTest.cs
new file mode 100644
index 0000000..63baab3
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/DefaultDeserializationContextTest.cs
@@ -0,0 +1,240 @@
+#region Copyright notice and license
+
+// Copyright 2019 The 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.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+using System.Runtime.InteropServices;
+
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+using System.Buffers;
+#endif
+
+namespace Grpc.Core.Internal.Tests
+{
+    public class DefaultDeserializationContextTest
+    {
+        FakeBufferReaderManager fakeBufferReaderManager;
+
+        [SetUp]
+        public void Init()
+        {
+            fakeBufferReaderManager = new FakeBufferReaderManager();
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            fakeBufferReaderManager.Dispose();
+        }
+
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+        [TestCase]
+        public void PayloadAsReadOnlySequence_ZeroSegmentPayload()
+        {
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> {}));
+
+            Assert.AreEqual(0, context.PayloadLength);
+
+            var sequence = context.PayloadAsReadOnlySequence();
+
+            Assert.AreEqual(ReadOnlySequence<byte>.Empty, sequence);
+            Assert.IsTrue(sequence.IsEmpty);
+            Assert.IsTrue(sequence.IsSingleSegment);
+        }
+
+        [TestCase(0)]
+        [TestCase(1)]
+        [TestCase(10)]
+        [TestCase(100)]
+        [TestCase(1000)]
+        public void PayloadAsReadOnlySequence_SingleSegmentPayload(int segmentLength)
+        {
+            var origBuffer = GetTestBuffer(segmentLength);
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer));
+
+            Assert.AreEqual(origBuffer.Length, context.PayloadLength);
+
+            var sequence = context.PayloadAsReadOnlySequence();
+
+            Assert.AreEqual(origBuffer.Length, sequence.Length);
+            Assert.AreEqual(origBuffer.Length, sequence.First.Length);
+            Assert.IsTrue(sequence.IsSingleSegment);
+            CollectionAssert.AreEqual(origBuffer, sequence.First.ToArray());
+        }
+
+        [TestCase(0, 5, 10)]
+        [TestCase(1, 1, 1)]
+        [TestCase(10, 100, 1000)]
+        [TestCase(100, 100, 10)]
+        [TestCase(1000, 1000, 1000)]
+        public void PayloadAsReadOnlySequence_MultiSegmentPayload(int segmentLen1, int segmentLen2, int segmentLen3)
+        {
+            var origBuffer1 = GetTestBuffer(segmentLen1);
+            var origBuffer2 = GetTestBuffer(segmentLen2);
+            var origBuffer3 = GetTestBuffer(segmentLen3);
+            int totalLen = origBuffer1.Length + origBuffer2.Length + origBuffer3.Length;
+
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> { origBuffer1, origBuffer2, origBuffer3 }));
+
+            Assert.AreEqual(totalLen, context.PayloadLength);
+
+            var sequence = context.PayloadAsReadOnlySequence();
+
+            Assert.AreEqual(totalLen, sequence.Length);
+
+            var segmentEnumerator = sequence.GetEnumerator();
+
+            Assert.IsTrue(segmentEnumerator.MoveNext());
+            CollectionAssert.AreEqual(origBuffer1, segmentEnumerator.Current.ToArray());
+
+            Assert.IsTrue(segmentEnumerator.MoveNext());
+            CollectionAssert.AreEqual(origBuffer2, segmentEnumerator.Current.ToArray());
+
+            Assert.IsTrue(segmentEnumerator.MoveNext());
+            CollectionAssert.AreEqual(origBuffer3, segmentEnumerator.Current.ToArray());
+
+            Assert.IsFalse(segmentEnumerator.MoveNext());
+        }
+#endif
+
+        [TestCase]
+        public void NullPayloadNotAllowed()
+        {
+            var context = new DefaultDeserializationContext();
+            Assert.Throws(typeof(InvalidOperationException), () => context.Initialize(fakeBufferReaderManager.CreateNullPayloadBufferReader()));
+        }
+
+        [TestCase]
+        public void PayloadAsNewByteBuffer_ZeroSegmentPayload()
+        {
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> {}));
+
+            Assert.AreEqual(0, context.PayloadLength);
+
+            var payload = context.PayloadAsNewBuffer();
+            Assert.AreEqual(0, payload.Length);
+        }
+
+        [TestCase(0)]
+        [TestCase(1)]
+        [TestCase(10)]
+        [TestCase(100)]
+        [TestCase(1000)]
+        public void PayloadAsNewByteBuffer_SingleSegmentPayload(int segmentLength)
+        {
+            var origBuffer = GetTestBuffer(segmentLength);
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer));
+
+            Assert.AreEqual(origBuffer.Length, context.PayloadLength);
+
+            var payload = context.PayloadAsNewBuffer();
+            CollectionAssert.AreEqual(origBuffer, payload);
+        }
+
+        [TestCase(0, 5, 10)]
+        [TestCase(1, 1, 1)]
+        [TestCase(10, 100, 1000)]
+        [TestCase(100, 100, 10)]
+        [TestCase(1000, 1000, 1000)]
+        public void PayloadAsNewByteBuffer_MultiSegmentPayload(int segmentLen1, int segmentLen2, int segmentLen3)
+        {
+            var origBuffer1 = GetTestBuffer(segmentLen1);
+            var origBuffer2 = GetTestBuffer(segmentLen2);
+            var origBuffer3 = GetTestBuffer(segmentLen3);
+
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> { origBuffer1, origBuffer2, origBuffer3 }));
+
+            var payload = context.PayloadAsNewBuffer();
+
+            var concatenatedOrigBuffers = new List<byte>();
+            concatenatedOrigBuffers.AddRange(origBuffer1);
+            concatenatedOrigBuffers.AddRange(origBuffer2);
+            concatenatedOrigBuffers.AddRange(origBuffer3);
+
+            Assert.AreEqual(concatenatedOrigBuffers.Count, context.PayloadLength);
+            Assert.AreEqual(concatenatedOrigBuffers.Count, payload.Length);
+            CollectionAssert.AreEqual(concatenatedOrigBuffers, payload);
+        }
+
+        [TestCase]
+        public void GetPayloadMultipleTimesIsIllegal()
+        {
+            var origBuffer = GetTestBuffer(100);
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer));
+
+            Assert.AreEqual(origBuffer.Length, context.PayloadLength);
+
+            var payload = context.PayloadAsNewBuffer();
+            CollectionAssert.AreEqual(origBuffer, payload);
+
+            // Getting payload multiple times is illegal
+            Assert.Throws(typeof(InvalidOperationException), () => context.PayloadAsNewBuffer());
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+            Assert.Throws(typeof(InvalidOperationException), () => context.PayloadAsReadOnlySequence());
+#endif
+        }
+
+        [TestCase]
+        public void ResetContextAndReinitialize()
+        {
+            var origBuffer = GetTestBuffer(100);
+            var context = new DefaultDeserializationContext();
+            context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer));
+
+            Assert.AreEqual(origBuffer.Length, context.PayloadLength);
+
+            // Reset invalidates context
+            context.Reset();
+
+            Assert.AreEqual(0, context.PayloadLength);
+            Assert.Throws(typeof(NullReferenceException), () => context.PayloadAsNewBuffer());
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+            Assert.Throws(typeof(NullReferenceException), () => context.PayloadAsReadOnlySequence());
+#endif
+
+            // Previously reset context can be initialized again
+            var origBuffer2 = GetTestBuffer(50);
+            context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer2));
+
+            Assert.AreEqual(origBuffer2.Length, context.PayloadLength);
+            CollectionAssert.AreEqual(origBuffer2, context.PayloadAsNewBuffer());
+        }
+
+        private byte[] GetTestBuffer(int length)
+        {
+            var testBuffer = new byte[length];
+            for (int i = 0; i < testBuffer.Length; i++)
+            {
+                testBuffer[i] = (byte) i;
+            }
+            return testBuffer;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManager.cs b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManager.cs
new file mode 100644
index 0000000..d8d0c0a
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManager.cs
@@ -0,0 +1,118 @@
+#region Copyright notice and license
+
+// Copyright 2018 The 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.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal.Tests
+{
+    // Creates instances of fake IBufferReader. All created instances will become invalid once Dispose is called.
+    internal class FakeBufferReaderManager : IDisposable
+    {
+        List<GCHandle> pinnedHandles = new List<GCHandle>();
+        bool disposed = false;
+        public IBufferReader CreateSingleSegmentBufferReader(byte[] data)
+        {
+            return CreateMultiSegmentBufferReader(new List<byte[]> { data });
+        }
+
+        public IBufferReader CreateMultiSegmentBufferReader(IEnumerable<byte[]> dataSegments)
+        {
+            GrpcPreconditions.CheckState(!disposed);
+            GrpcPreconditions.CheckNotNull(dataSegments);
+            var segments = new List<GCHandle>();
+            foreach (var data in dataSegments)
+            {
+                GrpcPreconditions.CheckNotNull(data);
+                segments.Add(GCHandle.Alloc(data, GCHandleType.Pinned));
+            }
+            pinnedHandles.AddRange(segments);  // all the allocated GCHandles will be freed on Dispose()
+            return new FakeBufferReader(segments);
+        }
+
+        public IBufferReader CreateNullPayloadBufferReader()
+        {
+            GrpcPreconditions.CheckState(!disposed);
+            return new FakeBufferReader(null);
+        }
+
+        public void Dispose()
+        {
+            if (!disposed)
+            {
+                disposed = true;
+                for (int i = 0; i < pinnedHandles.Count; i++)
+                {
+                    pinnedHandles[i].Free();
+                }
+            }
+        }
+
+        private class FakeBufferReader : IBufferReader
+        {
+            readonly List<GCHandle> bufferSegments;
+            readonly int? totalLength;
+            readonly IEnumerator<GCHandle> segmentEnumerator;
+
+            public FakeBufferReader(List<GCHandle> bufferSegments)
+            {
+                this.bufferSegments = bufferSegments;
+                this.totalLength = ComputeTotalLength(bufferSegments);
+                this.segmentEnumerator = bufferSegments?.GetEnumerator();
+            }
+
+            public int? TotalLength => totalLength;
+
+            public bool TryGetNextSlice(out Slice slice)
+            {
+                GrpcPreconditions.CheckNotNull(bufferSegments);
+                if (!segmentEnumerator.MoveNext())
+                {
+                    slice = default(Slice);
+                    return false;
+                }
+
+                var segment = segmentEnumerator.Current;
+                int sliceLen = ((byte[]) segment.Target).Length;
+                slice = new Slice(segment.AddrOfPinnedObject(), sliceLen);
+                return true;
+            }
+
+            static int? ComputeTotalLength(List<GCHandle> bufferSegments)
+            {
+                if (bufferSegments == null)
+                {
+                    return null;
+                }
+
+                int sum = 0;
+                foreach (var segment in bufferSegments)
+                {
+                    var data = (byte[]) segment.Target;
+                    sum += data.Length;
+                }
+                return sum;
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManagerTest.cs b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManagerTest.cs
new file mode 100644
index 0000000..7c4ff65
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManagerTest.cs
@@ -0,0 +1,121 @@
+#region Copyright notice and license
+
+// Copyright 2018 The 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.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Internal.Tests
+{
+    public class FakeBufferReaderManagerTest
+    {
+        FakeBufferReaderManager fakeBufferReaderManager;
+
+        [SetUp]
+        public void Init()
+        {
+            fakeBufferReaderManager = new FakeBufferReaderManager();
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            fakeBufferReaderManager.Dispose();
+        }
+
+        [TestCase]
+        public void NullPayload()
+        {
+            var fakeBufferReader = fakeBufferReaderManager.CreateNullPayloadBufferReader();
+            Assert.IsFalse(fakeBufferReader.TotalLength.HasValue);
+            Assert.Throws(typeof(ArgumentNullException), () => fakeBufferReader.TryGetNextSlice(out Slice slice));
+        }
+        [TestCase]
+        public void ZeroSegmentPayload()
+        {
+            var fakeBufferReader = fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> {});
+            Assert.AreEqual(0, fakeBufferReader.TotalLength.Value);
+            Assert.IsFalse(fakeBufferReader.TryGetNextSlice(out Slice slice));
+        }
+
+        [TestCase(0)]
+        [TestCase(1)]
+        [TestCase(10)]
+        [TestCase(30)]
+        [TestCase(100)]
+        [TestCase(1000)]
+        public void SingleSegmentPayload(int bufferLen)
+        {
+            var origBuffer = GetTestBuffer(bufferLen);
+            var fakeBufferReader = fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer);
+            Assert.AreEqual(origBuffer.Length, fakeBufferReader.TotalLength.Value);
+
+            Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice));
+            AssertSliceDataEqual(origBuffer, slice);
+
+            Assert.IsFalse(fakeBufferReader.TryGetNextSlice(out Slice slice2));
+        }
+
+        [TestCase(0, 5, 10)]
+        [TestCase(1, 1, 1)]
+        [TestCase(10, 100, 1000)]
+        [TestCase(100, 100, 10)]
+        [TestCase(1000, 1000, 1000)]
+        public void MultiSegmentPayload(int segmentLen1, int segmentLen2, int segmentLen3)
+        {
+            var origBuffer1 = GetTestBuffer(segmentLen1);
+            var origBuffer2 = GetTestBuffer(segmentLen2);
+            var origBuffer3 = GetTestBuffer(segmentLen3);
+            var fakeBufferReader = fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> { origBuffer1, origBuffer2, origBuffer3 });
+
+            Assert.AreEqual(origBuffer1.Length + origBuffer2.Length + origBuffer3.Length, fakeBufferReader.TotalLength.Value);
+
+            Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice1));
+            AssertSliceDataEqual(origBuffer1, slice1);
+
+            Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice2));
+            AssertSliceDataEqual(origBuffer2, slice2);
+
+            Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice3));
+            AssertSliceDataEqual(origBuffer3, slice3);
+
+            Assert.IsFalse(fakeBufferReader.TryGetNextSlice(out Slice slice4));
+        }
+
+        private void AssertSliceDataEqual(byte[] expected, Slice actual)
+        {
+            var actualSliceData = new byte[actual.Length];
+            actual.CopyTo(new ArraySegment<byte>(actualSliceData));
+            CollectionAssert.AreEqual(expected, actualSliceData);
+        }
+
+        // create a buffer of given size and fill it with some data
+        private byte[] GetTestBuffer(int length)
+        {
+            var testBuffer = new byte[length];
+            for (int i = 0; i < testBuffer.Length; i++)
+            {
+                testBuffer[i] = (byte) i;
+            }
+            return testBuffer;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Internal/ReusableSliceBufferTest.cs b/src/csharp/Grpc.Core.Tests/Internal/ReusableSliceBufferTest.cs
new file mode 100644
index 0000000..7630785
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/ReusableSliceBufferTest.cs
@@ -0,0 +1,151 @@
+#region Copyright notice and license
+
+// Copyright 2018 The 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.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+using System.Runtime.InteropServices;
+
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+using System.Buffers;
+#endif
+
+namespace Grpc.Core.Internal.Tests
+{
+    // Converts IBufferReader into instances of ReadOnlySequence<byte>
+    // Objects representing the sequence segments are cached to decrease GC load.
+    public class ReusableSliceBufferTest
+    {
+        FakeBufferReaderManager fakeBufferReaderManager;
+
+        [SetUp]
+        public void Init()
+        {
+            fakeBufferReaderManager = new FakeBufferReaderManager();
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            fakeBufferReaderManager.Dispose();
+        }
+
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+        [TestCase]
+        public void NullPayload()
+        {
+            var sliceBuffer = new ReusableSliceBuffer();
+            Assert.Throws(typeof(ArgumentNullException), () => sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateNullPayloadBufferReader()));
+        }
+
+        [TestCase]
+        public void ZeroSegmentPayload()
+        {
+            var sliceBuffer = new ReusableSliceBuffer();
+            var sequence = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> {}));
+
+            Assert.AreEqual(ReadOnlySequence<byte>.Empty, sequence);
+            Assert.IsTrue(sequence.IsEmpty);
+            Assert.IsTrue(sequence.IsSingleSegment);
+        }
+
+        [TestCase]
+        public void SegmentsAreCached()
+        {
+            var bufferSegments1 = Enumerable.Range(0, 100).Select((_) => GetTestBuffer(50)).ToList();
+            var bufferSegments2 = Enumerable.Range(0, 100).Select((_) => GetTestBuffer(50)).ToList();
+
+            var sliceBuffer = new ReusableSliceBuffer();
+
+            var sequence1 = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(bufferSegments1));
+            var memoryManagers1 = GetMemoryManagersForSequenceSegments(sequence1);
+
+            sliceBuffer.Invalidate();
+
+            var sequence2 = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(bufferSegments2));
+            var memoryManagers2 = GetMemoryManagersForSequenceSegments(sequence2);
+
+            // check memory managers are identical objects (i.e. they've been reused)
+            CollectionAssert.AreEquivalent(memoryManagers1, memoryManagers2);
+        }
+
+        [TestCase]
+        public void MultiSegmentPayload_LotsOfSegments()
+        {
+            var bufferSegments = Enumerable.Range(0, ReusableSliceBuffer.MaxCachedSegments + 100).Select((_) => GetTestBuffer(10)).ToList();
+
+            var sliceBuffer = new ReusableSliceBuffer();
+            var sequence = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(bufferSegments));
+
+            int index = 0;
+            foreach (var memory in sequence)
+            {
+                CollectionAssert.AreEqual(bufferSegments[index], memory.ToArray());
+                index ++;
+            }
+        }
+
+        [TestCase]
+        public void InvalidateMakesSequenceUnusable()
+        {
+            var origBuffer = GetTestBuffer(100);
+
+            var sliceBuffer = new ReusableSliceBuffer();
+            var sequence = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List<byte[]> { origBuffer }));
+
+            Assert.AreEqual(origBuffer.Length, sequence.Length);
+
+            sliceBuffer.Invalidate();
+
+            // Invalidate with make the returned sequence completely unusable and broken, users must not use it beyond the deserializer functions.
+            Assert.Throws(typeof(ArgumentOutOfRangeException), () => { var first = sequence.First; });
+        }
+
+        private List<MemoryManager<byte>> GetMemoryManagersForSequenceSegments(ReadOnlySequence<byte> sequence)
+        {
+            var result = new List<MemoryManager<byte>>();
+            foreach (var memory in sequence)
+            {
+                Assert.IsTrue(MemoryMarshal.TryGetMemoryManager(memory, out MemoryManager<byte> memoryManager));
+                result.Add(memoryManager);
+            }
+            return result;
+        }
+#else
+        [TestCase]
+        public void OnlySupportedOnNetCore()
+        {
+            // Test case needs to exist to make C# sanity test happy.
+        }
+#endif
+        private byte[] GetTestBuffer(int length)
+        {
+            var testBuffer = new byte[length];
+            for (int i = 0; i < testBuffer.Length; i++)
+            {
+                testBuffer[i] = (byte) i;
+            }
+            return testBuffer;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Internal/SliceTest.cs b/src/csharp/Grpc.Core.Tests/Internal/SliceTest.cs
new file mode 100644
index 0000000..eb090bb
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/SliceTest.cs
@@ -0,0 +1,83 @@
+#region Copyright notice and license
+
+// Copyright 2018 The 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.
+
+#endregion
+
+using System;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+using System.Runtime.InteropServices;
+
+namespace Grpc.Core.Internal.Tests
+{
+    public class SliceTest
+    {
+        [TestCase(0)]
+        [TestCase(1)]
+        [TestCase(10)]
+        [TestCase(100)]
+        [TestCase(1000)]
+        public void SliceFromNativePtr_CopyToArraySegment(int bufferLength)
+        {
+            var origBuffer = GetTestBuffer(bufferLength);
+            var gcHandle = GCHandle.Alloc(origBuffer, GCHandleType.Pinned);
+            try
+            {
+                var slice = new Slice(gcHandle.AddrOfPinnedObject(), origBuffer.Length);
+                Assert.AreEqual(bufferLength, slice.Length);
+
+                var newBuffer = new byte[bufferLength];
+                slice.CopyTo(new ArraySegment<byte>(newBuffer));
+                CollectionAssert.AreEqual(origBuffer, newBuffer);
+            }
+            finally
+            {
+                gcHandle.Free();
+            }
+        }
+
+        [TestCase]
+        public void SliceFromNativePtr_CopyToArraySegmentTooSmall()
+        {
+            var origBuffer = GetTestBuffer(100);
+            var gcHandle = GCHandle.Alloc(origBuffer, GCHandleType.Pinned);
+            try
+            {
+                var slice = new Slice(gcHandle.AddrOfPinnedObject(), origBuffer.Length);
+                var tooSmall = new byte[origBuffer.Length - 1];
+                Assert.Catch(typeof(ArgumentException), () => slice.CopyTo(new ArraySegment<byte>(tooSmall)));
+            }
+            finally
+            {
+                gcHandle.Free();
+            }
+        }
+
+        // create a buffer of given size and fill it with some data
+        private byte[] GetTestBuffer(int length)
+        {
+            var testBuffer = new byte[length];
+            for (int i = 0; i < testBuffer.Length; i++)
+            {
+                testBuffer[i] = (byte) i;
+            }
+            return testBuffer;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/CallCredentials.cs b/src/csharp/Grpc.Core/CallCredentials.cs
deleted file mode 100644
index c1bd95b..0000000
--- a/src/csharp/Grpc.Core/CallCredentials.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-#region Copyright notice and license
-
-// 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.
-
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-using Grpc.Core.Internal;
-using Grpc.Core.Utils;
-
-namespace Grpc.Core
-{
-    /// <summary>
-    /// Client-side call credentials. Provide authorization with per-call granularity.
-    /// </summary>
-    public abstract class CallCredentials
-    {
-        /// <summary>
-        /// Composes multiple multiple <c>CallCredentials</c> objects into
-        /// a single <c>CallCredentials</c> object.
-        /// </summary>
-        /// <param name="credentials">credentials to compose</param>
-        /// <returns>The new <c>CompositeCallCredentials</c></returns>
-        public static CallCredentials Compose(params CallCredentials[] credentials)
-        {
-            return new CompositeCallCredentials(credentials);
-        }
-
-        /// <summary>
-        /// Creates a new instance of <c>CallCredentials</c> class from an
-        /// interceptor that can attach metadata to outgoing calls.
-        /// </summary>
-        /// <param name="interceptor">authentication interceptor</param>
-        public static CallCredentials FromInterceptor(AsyncAuthInterceptor interceptor)
-        {
-            return new MetadataCredentials(interceptor);
-        }
-
-        /// <summary>
-        /// Creates native object for the credentials.
-        /// </summary>
-        /// <returns>The native credentials.</returns>
-        internal abstract CallCredentialsSafeHandle ToNativeCredentials();
-    }
-
-    /// <summary>
-    /// Client-side credentials that delegate metadata based auth to an interceptor.
-    /// The interceptor is automatically invoked for each remote call that uses <c>MetadataCredentials.</c>
-    /// </summary>
-    internal sealed class MetadataCredentials : CallCredentials
-    {
-        readonly AsyncAuthInterceptor interceptor;
-
-        /// <summary>
-        /// Initializes a new instance of <c>MetadataCredentials</c> class.
-        /// </summary>
-        /// <param name="interceptor">authentication interceptor</param>
-        public MetadataCredentials(AsyncAuthInterceptor interceptor)
-        {
-            this.interceptor = GrpcPreconditions.CheckNotNull(interceptor);
-        }
-
-        internal override CallCredentialsSafeHandle ToNativeCredentials()
-        {
-            NativeMetadataCredentialsPlugin plugin = new NativeMetadataCredentialsPlugin(interceptor);
-            return plugin.Credentials;
-        }
-    }
-
-    /// <summary>
-    /// Credentials that allow composing multiple credentials objects into one <see cref="CallCredentials"/> object.
-    /// </summary>
-    internal sealed class CompositeCallCredentials : CallCredentials
-    {
-        readonly List<CallCredentials> credentials;
-
-        /// <summary>
-        /// Initializes a new instance of <c>CompositeCallCredentials</c> class.
-        /// The resulting credentials object will be composite of all the credentials specified as parameters.
-        /// </summary>
-        /// <param name="credentials">credentials to compose</param>
-        public CompositeCallCredentials(params CallCredentials[] credentials)
-        {
-            GrpcPreconditions.CheckArgument(credentials.Length >= 2, "Composite credentials object can only be created from 2 or more credentials.");
-            this.credentials = new List<CallCredentials>(credentials);
-        }
-
-        internal override CallCredentialsSafeHandle ToNativeCredentials()
-        {
-            return ToNativeRecursive(0);
-        }
-
-        // Recursive descent makes managing lifetime of intermediate CredentialSafeHandle instances easier.
-        // In practice, we won't usually see composites from more than two credentials anyway.
-        private CallCredentialsSafeHandle ToNativeRecursive(int startIndex)
-        {
-            if (startIndex == credentials.Count - 1)
-            {
-                return credentials[startIndex].ToNativeCredentials();
-            }
-
-            using (var cred1 = credentials[startIndex].ToNativeCredentials())
-            using (var cred2 = ToNativeRecursive(startIndex + 1))
-            {
-                var nativeComposite = CallCredentialsSafeHandle.CreateComposite(cred1, cred2);
-                if (nativeComposite.IsInvalid)
-                {
-                    throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials.");
-                }
-                return nativeComposite;
-            }
-        }
-    }
-}
diff --git a/src/csharp/Grpc.Core/ChannelCredentials.cs b/src/csharp/Grpc.Core/ChannelCredentials.cs
index 3ce32f3..dcbe9f3 100644
--- a/src/csharp/Grpc.Core/ChannelCredentials.cs
+++ b/src/csharp/Grpc.Core/ChannelCredentials.cs
@@ -18,9 +18,11 @@
 
 using System;
 using System.Collections.Generic;
+using System.Runtime.InteropServices;
 using System.Threading.Tasks;
 
 using Grpc.Core.Internal;
+using Grpc.Core.Logging;
 using Grpc.Core.Utils;
 
 namespace Grpc.Core
@@ -105,19 +107,37 @@
     }
 
     /// <summary>
+    /// Callback invoked with the expected targetHost and the peer's certificate.
+    /// If false is returned by this callback then it is treated as a
+    /// verification failure and the attempted connection will fail.
+    /// Invocation of the callback is blocking, so any
+    /// implementation should be light-weight.
+    /// Note that the callback can potentially be invoked multiple times,
+    /// concurrently from different threads (e.g. when multiple connections
+    /// are being created for the same credentials).
+    /// </summary>
+    /// <param name="context">The <see cref="T:Grpc.Core.VerifyPeerContext"/> associated with the callback</param>
+    /// <returns>true if verification succeeded, false otherwise.</returns>
+    /// Note: experimental API that can change or be removed without any prior notice.
+    public delegate bool VerifyPeerCallback(VerifyPeerContext context);
+
+    /// <summary>
     /// Client-side SSL credentials.
     /// </summary>
     public sealed class SslCredentials : ChannelCredentials
     {
+        static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<SslCredentials>();
+
         readonly string rootCertificates;
         readonly KeyCertificatePair keyCertificatePair;
+        readonly VerifyPeerCallback verifyPeerCallback;
 
         /// <summary>
         /// Creates client-side SSL credentials loaded from
         /// disk file pointed to by the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable.
         /// If that fails, gets the roots certificates from a well known place on disk.
         /// </summary>
-        public SslCredentials() : this(null, null)
+        public SslCredentials() : this(null, null, null)
         {
         }
 
@@ -125,19 +145,32 @@
         /// Creates client-side SSL credentials from
         /// a string containing PEM encoded root certificates.
         /// </summary>
-        public SslCredentials(string rootCertificates) : this(rootCertificates, null)
+        public SslCredentials(string rootCertificates) : this(rootCertificates, null, null)
         {
         }
-            
+
         /// <summary>
         /// Creates client-side SSL credentials.
         /// </summary>
         /// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
         /// <param name="keyCertificatePair">a key certificate pair.</param>
-        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair)
+        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair) :
+            this(rootCertificates, keyCertificatePair, null)
+        {
+        }
+
+        /// <summary>
+        /// Creates client-side SSL credentials.
+        /// </summary>
+        /// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
+        /// <param name="keyCertificatePair">a key certificate pair.</param>
+        /// <param name="verifyPeerCallback">a callback to verify peer's target name and certificate.</param>
+        /// Note: experimental API that can change or be removed without any prior notice.
+        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback)
         {
             this.rootCertificates = rootCertificates;
             this.keyCertificatePair = keyCertificatePair;
+            this.verifyPeerCallback = verifyPeerCallback;
         }
 
         /// <summary>
@@ -171,7 +204,54 @@
 
         internal override ChannelCredentialsSafeHandle CreateNativeCredentials()
         {
-            return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair);
+            IntPtr verifyPeerCallbackTag = IntPtr.Zero;
+            if (verifyPeerCallback != null)
+            {
+                verifyPeerCallbackTag = new VerifyPeerCallbackRegistration(verifyPeerCallback).CallbackRegistration.Tag;
+            }
+            return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair, verifyPeerCallbackTag);
+        }
+
+        private class VerifyPeerCallbackRegistration
+        {
+            readonly VerifyPeerCallback verifyPeerCallback;
+            readonly NativeCallbackRegistration callbackRegistration;
+
+            public VerifyPeerCallbackRegistration(VerifyPeerCallback verifyPeerCallback)
+            {
+                this.verifyPeerCallback = verifyPeerCallback;
+                this.callbackRegistration = NativeCallbackDispatcher.RegisterCallback(HandleUniversalCallback);
+            }
+
+            public NativeCallbackRegistration CallbackRegistration => callbackRegistration;
+
+            private int HandleUniversalCallback(IntPtr arg0, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5)
+            {
+                return VerifyPeerCallbackHandler(arg0, arg1, arg2 != IntPtr.Zero);
+            }
+
+            private int VerifyPeerCallbackHandler(IntPtr targetName, IntPtr peerPem, bool isDestroy)
+            {
+                if (isDestroy)
+                {
+                    this.callbackRegistration.Dispose();
+                    return 0;
+                }
+
+                try
+                {
+                    var context = new VerifyPeerContext(Marshal.PtrToStringAnsi(targetName), Marshal.PtrToStringAnsi(peerPem));
+
+                    return this.verifyPeerCallback(context) ? 0 : 1;
+                }
+                catch (Exception e)
+                {
+                    // eat the exception, we must not throw when inside callback from native code.
+                    Logger.Error(e, "Exception occurred while invoking verify peer callback handler.");
+                    // Return validation failure in case of exception.
+                    return 1;
+                }
+            }
         }
     }
 
diff --git a/src/csharp/Grpc.Core/ForwardedTypes.cs b/src/csharp/Grpc.Core/ForwardedTypes.cs
index 3922192..ab5f5a8 100644
--- a/src/csharp/Grpc.Core/ForwardedTypes.cs
+++ b/src/csharp/Grpc.Core/ForwardedTypes.cs
@@ -18,21 +18,33 @@
 
 using System.Runtime.CompilerServices;
 using Grpc.Core;
-using Grpc.Core.Logging;
+using Grpc.Core.Interceptors;
+using Grpc.Core.Internal;
 using Grpc.Core.Utils;
 
 // API types that used to be in Grpc.Core package, but were moved to Grpc.Core.Api
 // https://docs.microsoft.com/en-us/dotnet/framework/app-domains/type-forwarding-in-the-common-language-runtime
 
-// TODO(jtattermusch): move types needed for implementing a client
-
 [assembly:TypeForwardedToAttribute(typeof(GrpcPreconditions))]
+[assembly:TypeForwardedToAttribute(typeof(AsyncClientStreamingCall<,>))]
+[assembly:TypeForwardedToAttribute(typeof(AsyncDuplexStreamingCall<,>))]
+[assembly:TypeForwardedToAttribute(typeof(AsyncServerStreamingCall<>))]
+[assembly:TypeForwardedToAttribute(typeof(AsyncUnaryCall<>))]
 [assembly:TypeForwardedToAttribute(typeof(AuthContext))]
+[assembly:TypeForwardedToAttribute(typeof(AsyncAuthInterceptor))]
+[assembly:TypeForwardedToAttribute(typeof(AuthInterceptorContext))]
+[assembly:TypeForwardedToAttribute(typeof(CallCredentials))]
+[assembly:TypeForwardedToAttribute(typeof(CallFlags))]
+[assembly:TypeForwardedToAttribute(typeof(CallInvoker))]
+[assembly:TypeForwardedToAttribute(typeof(CallOptions))]
+[assembly:TypeForwardedToAttribute(typeof(ClientInterceptorContext<,>))]
 [assembly:TypeForwardedToAttribute(typeof(ContextPropagationOptions))]
 [assembly:TypeForwardedToAttribute(typeof(ContextPropagationToken))]
 [assembly:TypeForwardedToAttribute(typeof(DeserializationContext))]
 [assembly:TypeForwardedToAttribute(typeof(IAsyncStreamReader<>))]
 [assembly:TypeForwardedToAttribute(typeof(IAsyncStreamWriter<>))]
+[assembly:TypeForwardedToAttribute(typeof(IClientStreamWriter<>))]
+[assembly:TypeForwardedToAttribute(typeof(Interceptor))]
 [assembly:TypeForwardedToAttribute(typeof(IServerStreamWriter<>))]
 [assembly:TypeForwardedToAttribute(typeof(Marshaller<>))]
 [assembly:TypeForwardedToAttribute(typeof(Marshallers))]
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index b7c191e..afd60e7 100755
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -19,6 +19,15 @@
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
 
+  <PropertyGroup>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
+    <LangVersion>7.2</LangVersion>
+    <DefineConstants>$(DefineConstants);GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY</DefineConstants>
+  </PropertyGroup>
+
   <ItemGroup>
     <Compile Include="..\Grpc.Core.Api\Version.cs" />
   </ItemGroup>
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 785081c..a1c6881 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -111,7 +111,7 @@
                             {
                                 using (profiler.NewScope("AsyncCall.UnaryCall.HandleBatch"))
                                 {
-                                    HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessage(), ctx.GetReceivedInitialMetadata());
+                                    HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessageReader(), ctx.GetReceivedInitialMetadata());
                                 }
                             }
                             catch (Exception e)
@@ -537,14 +537,14 @@
         /// <summary>
         /// Handler for unary response completion.
         /// </summary>
-        private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
+        private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, IBufferReader receivedMessageReader, Metadata responseHeaders)
         {
             // NOTE: because this event is a result of batch containing GRPC_OP_RECV_STATUS_ON_CLIENT,
             // success will be always set to true.
 
             TaskCompletionSource<object> delayedStreamingWriteTcs = null;
             TResponse msg = default(TResponse);
-            var deserializeException = TryDeserialize(receivedMessage, out msg);
+            var deserializeException = TryDeserialize(receivedMessageReader, out msg);
 
             bool releasedResources;
             lock (myLock)
@@ -634,9 +634,9 @@
 
         IUnaryResponseClientCallback UnaryResponseClientCallback => this;
 
-        void IUnaryResponseClientCallback.OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
+        void IUnaryResponseClientCallback.OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, IBufferReader receivedMessageReader, Metadata responseHeaders)
         {
-            HandleUnaryResponse(success, receivedStatus, receivedMessage, responseHeaders);
+            HandleUnaryResponse(success, receivedStatus, receivedMessageReader, responseHeaders);
         }
 
         IReceivedStatusOnClientCallback ReceivedStatusOnClientCallback => this;
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index 39c9f7c..0b99aaf 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -228,12 +228,12 @@
             }
         }
 
-        protected Exception TryDeserialize(byte[] payload, out TRead msg)
+        protected Exception TryDeserialize(IBufferReader reader, out TRead msg)
         {
             DefaultDeserializationContext context = null;
             try
             {
-                context = DefaultDeserializationContext.GetInitializedThreadLocal(payload);
+                context = DefaultDeserializationContext.GetInitializedThreadLocal(reader);
                 msg = deserializer(context);
                 return null;
             }
@@ -245,7 +245,6 @@
             finally
             {
                 context?.Reset();
-
             }
         }
 
@@ -333,21 +332,21 @@
         /// <summary>
         /// Handles streaming read completion.
         /// </summary>
-        protected void HandleReadFinished(bool success, byte[] receivedMessage)
+        protected void HandleReadFinished(bool success, IBufferReader receivedMessageReader)
         {
-            // if success == false, received message will be null. It that case we will
+            // if success == false, the message reader will report null payload. It that case we will
             // treat this completion as the last read an rely on C core to handle the failed
             // read (e.g. deliver approriate statusCode on the clientside).
 
             TRead msg = default(TRead);
-            var deserializeException = (success && receivedMessage != null) ? TryDeserialize(receivedMessage, out msg) : null;
+            var deserializeException = (success && receivedMessageReader.TotalLength.HasValue) ? TryDeserialize(receivedMessageReader, out msg) : null;
 
             TaskCompletionSource<TRead> origTcs = null;
             bool releasedResources;
             lock (myLock)
             {
                 origTcs = streamingReadTcs;
-                if (receivedMessage == null)
+                if (!receivedMessageReader.TotalLength.HasValue)
                 {
                     // This was the last read.
                     readingDone = true;
@@ -391,9 +390,9 @@
 
         IReceivedMessageCallback ReceivedMessageCallback => this;
 
-        void IReceivedMessageCallback.OnReceivedMessage(bool success, byte[] receivedMessage)
+        void IReceivedMessageCallback.OnReceivedMessage(bool success, IBufferReader receivedMessageReader)
         {
-            HandleReadFinished(success, receivedMessage);
+            HandleReadFinished(success, receivedMessageReader);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
index 085e7fa..50a6268 100644
--- a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
@@ -30,10 +30,17 @@
         void OnComplete(bool success);
     }
 
+    internal interface IBufferReader
+    {
+        int? TotalLength { get; }
+
+        bool TryGetNextSlice(out Slice slice);
+    }
+
     /// <summary>
     /// grpcsharp_batch_context
     /// </summary>
-    internal class BatchContextSafeHandle : SafeHandleZeroIsInvalid, IOpCompletionCallback, IPooledObject<BatchContextSafeHandle>
+    internal class BatchContextSafeHandle : SafeHandleZeroIsInvalid, IOpCompletionCallback, IPooledObject<BatchContextSafeHandle>, IBufferReader
     {
         static readonly NativeMethods Native = NativeMethods.Get();
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<BatchContextSafeHandle>();
@@ -93,17 +100,9 @@
             return new ClientSideStatus(status, metadata);
         }
 
-        // Gets data of recv_message completion.
-        public byte[] GetReceivedMessage()
+        public IBufferReader GetReceivedMessageReader()
         {
-            IntPtr len = Native.grpcsharp_batch_context_recv_message_length(this);
-            if (len == new IntPtr(-1))
-            {
-                return null;
-            }
-            byte[] data = new byte[(int)len];
-            Native.grpcsharp_batch_context_recv_message_to_buffer(this, data, new UIntPtr((ulong)data.Length));
-            return data;
+            return this;
         }
 
         // Gets data of receive_close_on_server completion.
@@ -153,6 +152,29 @@
             }
         }
 
+        int? IBufferReader.TotalLength
+        {
+            get
+            {
+                var len = Native.grpcsharp_batch_context_recv_message_length(this);
+                return len != new IntPtr(-1) ? (int?) len : null;
+            }
+        }
+
+        bool IBufferReader.TryGetNextSlice(out Slice slice)
+        {
+            UIntPtr sliceLen;
+            IntPtr sliceDataPtr;
+
+            if (0 == Native.grpcsharp_batch_context_recv_message_next_slice_peek(this, out sliceLen, out sliceDataPtr))
+            {
+                slice = default(Slice);
+                return false;
+            }
+            slice = new Slice(sliceDataPtr, (int) sliceLen);
+            return true;
+        }
+
         struct CompletionCallbackData
         {
             public CompletionCallbackData(BatchCompletionDelegate callback, object state)
diff --git a/src/csharp/Grpc.Core/Internal/CallOptionsExtensions.cs b/src/csharp/Grpc.Core/Internal/CallOptionsExtensions.cs
new file mode 100644
index 0000000..b58b041
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/CallOptionsExtensions.cs
@@ -0,0 +1,57 @@
+#region Copyright notice and license
+
+// Copyright 2019 The 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.
+
+#endregion
+
+using System;

+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal
+{
+    internal static class CallOptionsExtensions
+    {
+        /// <summary>
+        /// Returns a new instance of <see cref="CallOptions"/> with
+        /// all previously unset values set to their defaults and deadline and cancellation
+        /// token propagated when appropriate.
+        /// </summary>
+        internal static CallOptions Normalize(this CallOptions options)
+        {
+            var newOptions = options;
+            // silently ignore the context propagation token if it wasn't produced by "us"
+            var propagationTokenImpl = options.PropagationToken.AsImplOrNull();
+            if (propagationTokenImpl != null)
+            {
+                if (propagationTokenImpl.Options.IsPropagateDeadline)
+                {
+                    GrpcPreconditions.CheckArgument(!newOptions.Deadline.HasValue,
+                        "Cannot propagate deadline from parent call. The deadline has already been set explicitly.");
+                    newOptions = newOptions.WithDeadline(propagationTokenImpl.ParentDeadline);
+                }
+                if (propagationTokenImpl.Options.IsPropagateCancellation)
+                {
+                    GrpcPreconditions.CheckArgument(!newOptions.CancellationToken.CanBeCanceled,
+                        "Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value.");
+                    newOptions = newOptions.WithCancellationToken(propagationTokenImpl.ParentCancellationToken);
+                }
+            }
+
+            newOptions = newOptions.WithHeaders(newOptions.Headers ?? Metadata.Empty);
+            newOptions = newOptions.WithDeadline(newOptions.Deadline ?? DateTime.MaxValue);
+            return newOptions;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index a3ef3e6..858d2a6 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -35,11 +35,11 @@
         // Completion handlers are pre-allocated to avoid unneccessary delegate allocations.
         // The "state" field is used to store the actual callback to invoke.
         static readonly BatchCompletionDelegate CompletionHandler_IUnaryResponseClientCallback =
-            (success, context, state) => ((IUnaryResponseClientCallback)state).OnUnaryResponseClient(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata());
+            (success, context, state) => ((IUnaryResponseClientCallback)state).OnUnaryResponseClient(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessageReader(), context.GetReceivedInitialMetadata());
         static readonly BatchCompletionDelegate CompletionHandler_IReceivedStatusOnClientCallback =
             (success, context, state) => ((IReceivedStatusOnClientCallback)state).OnReceivedStatusOnClient(success, context.GetReceivedStatusOnClient());
         static readonly BatchCompletionDelegate CompletionHandler_IReceivedMessageCallback =
-            (success, context, state) => ((IReceivedMessageCallback)state).OnReceivedMessage(success, context.GetReceivedMessage());
+            (success, context, state) => ((IReceivedMessageCallback)state).OnReceivedMessage(success, context.GetReceivedMessageReader());
         static readonly BatchCompletionDelegate CompletionHandler_IReceivedResponseHeadersCallback =
             (success, context, state) => ((IReceivedResponseHeadersCallback)state).OnReceivedResponseHeaders(success, context.GetReceivedInitialMetadata());
         static readonly BatchCompletionDelegate CompletionHandler_ISendCompletionCallback =
diff --git a/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
index 11b5d2c..d4f5034 100644
--- a/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
@@ -38,15 +38,15 @@
             return creds;
         }
 
-        public static ChannelCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, KeyCertificatePair keyCertPair)
+        public static ChannelCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, KeyCertificatePair keyCertPair, IntPtr verifyPeerCallbackTag)
         {
             if (keyCertPair != null)
             {
-                return Native.grpcsharp_ssl_credentials_create(pemRootCerts, keyCertPair.CertificateChain, keyCertPair.PrivateKey);
+                return Native.grpcsharp_ssl_credentials_create(pemRootCerts, keyCertPair.CertificateChain, keyCertPair.PrivateKey, verifyPeerCallbackTag);
             }
             else
             {
-                return Native.grpcsharp_ssl_credentials_create(pemRootCerts, null, null);
+                return Native.grpcsharp_ssl_credentials_create(pemRootCerts, null, null, verifyPeerCallbackTag);
             }
         }
 
diff --git a/src/csharp/Grpc.Core/Internal/DefaultCallCredentialsConfigurator.cs b/src/csharp/Grpc.Core/Internal/DefaultCallCredentialsConfigurator.cs
new file mode 100644
index 0000000..a2c53a1
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/DefaultCallCredentialsConfigurator.cs
@@ -0,0 +1,85 @@
+#region Copyright notice and license
+
+// Copyright 2019 The 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.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal
+{
+    /// <summary>
+    /// Creates native call credential objects from instances of <c>CallCredentials</c>.
+    /// </summary>
+    internal class DefaultCallCredentialsConfigurator : CallCredentialsConfiguratorBase
+    {
+        CallCredentialsSafeHandle nativeCredentials;
+
+        public CallCredentialsSafeHandle NativeCredentials => nativeCredentials;
+
+        public override void SetAsyncAuthInterceptorCredentials(object state, AsyncAuthInterceptor interceptor)
+        {
+            GrpcPreconditions.CheckState(nativeCredentials == null);
+
+            var plugin = new NativeMetadataCredentialsPlugin(interceptor);
+            nativeCredentials = plugin.Credentials;
+        }
+
+        public override void SetCompositeCredentials(object state, IReadOnlyList<CallCredentials> credentials)
+        {
+            GrpcPreconditions.CheckState(nativeCredentials == null);
+
+            GrpcPreconditions.CheckArgument(credentials.Count >= 2);
+            nativeCredentials = CompositeToNativeRecursive(credentials, 0);
+        }
+
+        // Recursive descent makes managing lifetime of intermediate CredentialSafeHandle instances easier.
+        // In practice, we won't usually see composites from more than two credentials anyway.
+        private CallCredentialsSafeHandle CompositeToNativeRecursive(IReadOnlyList<CallCredentials> credentials, int startIndex)
+        {
+            if (startIndex == credentials.Count - 1)
+            {
+                return credentials[startIndex].ToNativeCredentials();
+            }
+
+            using (var cred1 = credentials[startIndex].ToNativeCredentials())
+            using (var cred2 = CompositeToNativeRecursive(credentials, startIndex + 1))
+            {
+                var nativeComposite = CallCredentialsSafeHandle.CreateComposite(cred1, cred2);
+                if (nativeComposite.IsInvalid)
+                {
+                    throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials.");
+                }
+                return nativeComposite;
+            }
+        }
+    }
+
+    internal static class CallCredentialsExtensions
+    {
+        /// <summary>
+        /// Creates native object for the credentials.
+        /// </summary>
+        /// <returns>The native credentials.</returns>
+        public static CallCredentialsSafeHandle ToNativeCredentials(this CallCredentials credentials)
+        {
+            var configurator = new DefaultCallCredentialsConfigurator();
+            credentials.InternalPopulateConfiguration(configurator, credentials);
+            return configurator.NativeCredentials;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs b/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs
index 7ace80e..946c37d 100644
--- a/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs
+++ b/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs
@@ -20,6 +20,10 @@
 using System;
 using System.Threading;
 
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+using System.Buffers;
+#endif
+
 namespace Grpc.Core.Internal
 {
     internal class DefaultDeserializationContext : DeserializationContext
@@ -27,40 +31,71 @@
         static readonly ThreadLocal<DefaultDeserializationContext> threadLocalInstance =
             new ThreadLocal<DefaultDeserializationContext>(() => new DefaultDeserializationContext(), false);
 
-        byte[] payload;
-        bool alreadyCalledPayloadAsNewBuffer;
+        IBufferReader bufferReader;
+        int payloadLength;
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+        ReusableSliceBuffer cachedSliceBuffer = new ReusableSliceBuffer();
+#endif
 
         public DefaultDeserializationContext()
         {
             Reset();
         }
 
-        public override int PayloadLength => payload.Length;
+        public override int PayloadLength => payloadLength;
 
         public override byte[] PayloadAsNewBuffer()
         {
-            GrpcPreconditions.CheckState(!alreadyCalledPayloadAsNewBuffer);
-            alreadyCalledPayloadAsNewBuffer = true;
-            return payload;
+            var buffer = new byte[payloadLength];
+            FillContinguousBuffer(bufferReader, buffer);
+            return buffer;
         }
 
-        public void Initialize(byte[] payload)
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+        public override ReadOnlySequence<byte> PayloadAsReadOnlySequence()
         {
-            this.payload = GrpcPreconditions.CheckNotNull(payload);
-            this.alreadyCalledPayloadAsNewBuffer = false;
+            var sequence = cachedSliceBuffer.PopulateFrom(bufferReader);
+            GrpcPreconditions.CheckState(sequence.Length == payloadLength);
+            return sequence;
+        }
+#endif
+
+        public void Initialize(IBufferReader bufferReader)
+        {
+            this.bufferReader = GrpcPreconditions.CheckNotNull(bufferReader);
+            this.payloadLength = bufferReader.TotalLength.Value;  // payload must not be null
         }
 
         public void Reset()
         {
-            this.payload = null;
-            this.alreadyCalledPayloadAsNewBuffer = true;  // mark payload as read
+            this.bufferReader = null;
+            this.payloadLength = 0;
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+            this.cachedSliceBuffer.Invalidate();
+#endif
         }
 
-        public static DefaultDeserializationContext GetInitializedThreadLocal(byte[] payload)
+        public static DefaultDeserializationContext GetInitializedThreadLocal(IBufferReader bufferReader)
         {
             var instance = threadLocalInstance.Value;
-            instance.Initialize(payload);
+            instance.Initialize(bufferReader);
             return instance;
         }
+
+        private void FillContinguousBuffer(IBufferReader reader, byte[] destination)
+        {
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+            PayloadAsReadOnlySequence().CopyTo(new Span<byte>(destination));
+#else
+            int offset = 0;
+            while (reader.TryGetNextSlice(out Slice slice))
+            {
+                slice.CopyTo(new ArraySegment<byte>(destination, offset, (int)slice.Length));
+                offset += (int)slice.Length;
+            }
+            // check that we filled the entire destination
+            GrpcPreconditions.CheckState(offset == payloadLength);
+#endif
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/INativeCall.cs b/src/csharp/Grpc.Core/Internal/INativeCall.cs
index 5c35b2b..98117c6 100644
--- a/src/csharp/Grpc.Core/Internal/INativeCall.cs
+++ b/src/csharp/Grpc.Core/Internal/INativeCall.cs
@@ -22,7 +22,7 @@
 {
     internal interface IUnaryResponseClientCallback
     {
-        void OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders);
+        void OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, IBufferReader receivedMessageReader, Metadata responseHeaders);
     }
 
     // Received status for streaming response calls.
@@ -33,7 +33,7 @@
 
     internal interface IReceivedMessageCallback
     {
-        void OnReceivedMessage(bool success, byte[] receivedMessage);
+        void OnReceivedMessage(bool success, IBufferReader receivedMessageReader);
     }
 
     internal interface IReceivedResponseHeadersCallback
diff --git a/src/csharp/Grpc.Core/Internal/NativeCallbackDispatcher.cs b/src/csharp/Grpc.Core/Internal/NativeCallbackDispatcher.cs
index d5146e8..0777d79 100644
--- a/src/csharp/Grpc.Core/Internal/NativeCallbackDispatcher.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeCallbackDispatcher.cs
@@ -63,7 +63,7 @@
             catch (Exception e)
             {
                 // eat the exception, we must not throw when inside callback from native code.
-                Logger.Error(e, "Caught exception inside callback from native callback.");
+                Logger.Error(e, "Caught exception inside callback from native code.");
                 return 0;
             }
         }
diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
index b7b9a12..b8a60b3 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
@@ -40,7 +40,7 @@
         public readonly Delegates.grpcsharp_batch_context_create_delegate grpcsharp_batch_context_create;
         public readonly Delegates.grpcsharp_batch_context_recv_initial_metadata_delegate grpcsharp_batch_context_recv_initial_metadata;
         public readonly Delegates.grpcsharp_batch_context_recv_message_length_delegate grpcsharp_batch_context_recv_message_length;
-        public readonly Delegates.grpcsharp_batch_context_recv_message_to_buffer_delegate grpcsharp_batch_context_recv_message_to_buffer;
+        public readonly Delegates.grpcsharp_batch_context_recv_message_next_slice_peek_delegate grpcsharp_batch_context_recv_message_next_slice_peek;
         public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_status_delegate grpcsharp_batch_context_recv_status_on_client_status;
         public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_details_delegate grpcsharp_batch_context_recv_status_on_client_details;
         public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate grpcsharp_batch_context_recv_status_on_client_trailing_metadata;
@@ -141,7 +141,7 @@
             this.grpcsharp_batch_context_create = GetMethodDelegate<Delegates.grpcsharp_batch_context_create_delegate>(library);
             this.grpcsharp_batch_context_recv_initial_metadata = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_initial_metadata_delegate>(library);
             this.grpcsharp_batch_context_recv_message_length = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_message_length_delegate>(library);
-            this.grpcsharp_batch_context_recv_message_to_buffer = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_message_to_buffer_delegate>(library);
+            this.grpcsharp_batch_context_recv_message_next_slice_peek = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_message_next_slice_peek_delegate>(library);
             this.grpcsharp_batch_context_recv_status_on_client_status = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_status_on_client_status_delegate>(library);
             this.grpcsharp_batch_context_recv_status_on_client_details = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_status_on_client_details_delegate>(library);
             this.grpcsharp_batch_context_recv_status_on_client_trailing_metadata = GetMethodDelegate<Delegates.grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate>(library);
@@ -241,7 +241,7 @@
             this.grpcsharp_batch_context_create = DllImportsFromStaticLib.grpcsharp_batch_context_create;
             this.grpcsharp_batch_context_recv_initial_metadata = DllImportsFromStaticLib.grpcsharp_batch_context_recv_initial_metadata;
             this.grpcsharp_batch_context_recv_message_length = DllImportsFromStaticLib.grpcsharp_batch_context_recv_message_length;
-            this.grpcsharp_batch_context_recv_message_to_buffer = DllImportsFromStaticLib.grpcsharp_batch_context_recv_message_to_buffer;
+            this.grpcsharp_batch_context_recv_message_next_slice_peek = DllImportsFromStaticLib.grpcsharp_batch_context_recv_message_next_slice_peek;
             this.grpcsharp_batch_context_recv_status_on_client_status = DllImportsFromStaticLib.grpcsharp_batch_context_recv_status_on_client_status;
             this.grpcsharp_batch_context_recv_status_on_client_details = DllImportsFromStaticLib.grpcsharp_batch_context_recv_status_on_client_details;
             this.grpcsharp_batch_context_recv_status_on_client_trailing_metadata = DllImportsFromStaticLib.grpcsharp_batch_context_recv_status_on_client_trailing_metadata;
@@ -341,7 +341,7 @@
             this.grpcsharp_batch_context_create = DllImportsFromSharedLib.grpcsharp_batch_context_create;
             this.grpcsharp_batch_context_recv_initial_metadata = DllImportsFromSharedLib.grpcsharp_batch_context_recv_initial_metadata;
             this.grpcsharp_batch_context_recv_message_length = DllImportsFromSharedLib.grpcsharp_batch_context_recv_message_length;
-            this.grpcsharp_batch_context_recv_message_to_buffer = DllImportsFromSharedLib.grpcsharp_batch_context_recv_message_to_buffer;
+            this.grpcsharp_batch_context_recv_message_next_slice_peek = DllImportsFromSharedLib.grpcsharp_batch_context_recv_message_next_slice_peek;
             this.grpcsharp_batch_context_recv_status_on_client_status = DllImportsFromSharedLib.grpcsharp_batch_context_recv_status_on_client_status;
             this.grpcsharp_batch_context_recv_status_on_client_details = DllImportsFromSharedLib.grpcsharp_batch_context_recv_status_on_client_details;
             this.grpcsharp_batch_context_recv_status_on_client_trailing_metadata = DllImportsFromSharedLib.grpcsharp_batch_context_recv_status_on_client_trailing_metadata;
@@ -444,7 +444,7 @@
             public delegate BatchContextSafeHandle grpcsharp_batch_context_create_delegate();
             public delegate IntPtr grpcsharp_batch_context_recv_initial_metadata_delegate(BatchContextSafeHandle ctx);
             public delegate IntPtr grpcsharp_batch_context_recv_message_length_delegate(BatchContextSafeHandle ctx);
-            public delegate void grpcsharp_batch_context_recv_message_to_buffer_delegate(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen);
+            public delegate int grpcsharp_batch_context_recv_message_next_slice_peek_delegate(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr);
             public delegate StatusCode grpcsharp_batch_context_recv_status_on_client_status_delegate(BatchContextSafeHandle ctx);
             public delegate IntPtr grpcsharp_batch_context_recv_status_on_client_details_delegate(BatchContextSafeHandle ctx, out UIntPtr detailsLength);
             public delegate IntPtr grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate(BatchContextSafeHandle ctx);
@@ -482,7 +482,7 @@
             public delegate void grpcsharp_channel_args_set_integer_delegate(ChannelArgsSafeHandle args, UIntPtr index, string key, int value);
             public delegate void grpcsharp_channel_args_destroy_delegate(IntPtr args);
             public delegate void grpcsharp_override_default_ssl_roots_delegate(string pemRootCerts);
-            public delegate ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create_delegate(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
+            public delegate ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create_delegate(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, IntPtr verifyPeerCallbackTag);
             public delegate ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create_delegate(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds);
             public delegate void grpcsharp_channel_credentials_release_delegate(IntPtr credentials);
             public delegate ChannelSafeHandle grpcsharp_insecure_channel_create_delegate(string target, ChannelArgsSafeHandle channelArgs);
@@ -562,7 +562,7 @@
             public static extern IntPtr grpcsharp_batch_context_recv_message_length(BatchContextSafeHandle ctx);
             
             [DllImport(ImportName)]
-            public static extern void grpcsharp_batch_context_recv_message_to_buffer(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen);
+            public static extern int grpcsharp_batch_context_recv_message_next_slice_peek(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr);
             
             [DllImport(ImportName)]
             public static extern StatusCode grpcsharp_batch_context_recv_status_on_client_status(BatchContextSafeHandle ctx);
@@ -676,7 +676,7 @@
             public static extern void grpcsharp_override_default_ssl_roots(string pemRootCerts);
             
             [DllImport(ImportName)]
-            public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
+            public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, IntPtr verifyPeerCallbackTag);
             
             [DllImport(ImportName)]
             public static extern ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds);
@@ -858,7 +858,7 @@
             public static extern IntPtr grpcsharp_batch_context_recv_message_length(BatchContextSafeHandle ctx);
             
             [DllImport(ImportName)]
-            public static extern void grpcsharp_batch_context_recv_message_to_buffer(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen);
+            public static extern int grpcsharp_batch_context_recv_message_next_slice_peek(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr);
             
             [DllImport(ImportName)]
             public static extern StatusCode grpcsharp_batch_context_recv_status_on_client_status(BatchContextSafeHandle ctx);
@@ -972,7 +972,7 @@
             public static extern void grpcsharp_override_default_ssl_roots(string pemRootCerts);
             
             [DllImport(ImportName)]
-            public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
+            public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, IntPtr verifyPeerCallbackTag);
             
             [DllImport(ImportName)]
             public static extern ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds);
diff --git a/src/csharp/Grpc.Core/Internal/ReusableSliceBuffer.cs b/src/csharp/Grpc.Core/Internal/ReusableSliceBuffer.cs
new file mode 100644
index 0000000..2d38509
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/ReusableSliceBuffer.cs
@@ -0,0 +1,148 @@
+#region Copyright notice and license
+
+// Copyright 2019 The 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.
+
+#endregion
+
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+
+using Grpc.Core.Utils;
+using System;
+using System.Threading;
+
+using System.Buffers;
+
+namespace Grpc.Core.Internal
+{
+    internal class ReusableSliceBuffer
+    {
+        public const int MaxCachedSegments = 1024;  // ~4MB payload for 4K slices
+
+        readonly SliceSegment[] cachedSegments = new SliceSegment[MaxCachedSegments];
+        int populatedSegmentCount;
+
+        public ReadOnlySequence<byte> PopulateFrom(IBufferReader bufferReader)
+        {
+            populatedSegmentCount = 0;
+            long offset = 0;
+            SliceSegment prevSegment = null;
+            while (bufferReader.TryGetNextSlice(out Slice slice))
+            {
+                // Initialize cached segment if still null or just allocate a new segment if we already reached MaxCachedSegments
+                var current = populatedSegmentCount < cachedSegments.Length ? cachedSegments[populatedSegmentCount] : new SliceSegment();
+                if (current == null)
+                {
+                    current = cachedSegments[populatedSegmentCount] = new SliceSegment();
+                }
+
+                current.Reset(slice, offset);
+                prevSegment?.SetNext(current);
+
+                populatedSegmentCount ++;
+                offset += slice.Length;
+                prevSegment = current;
+            }
+
+            // Not necessary for ending the ReadOnlySequence, but for making sure we
+            // don't keep more than MaxCachedSegments alive.
+            prevSegment?.SetNext(null);
+
+            if (populatedSegmentCount == 0)
+            {
+                return ReadOnlySequence<byte>.Empty;
+            }
+
+            var firstSegment = cachedSegments[0];
+            var lastSegment = prevSegment;
+            return new ReadOnlySequence<byte>(firstSegment, 0, lastSegment, lastSegment.Memory.Length);
+        }
+
+        public void Invalidate()
+        {
+            if (populatedSegmentCount == 0)
+            {
+                return;
+            }
+            var segment = cachedSegments[0];
+            while (segment != null)
+            {
+                segment.Reset(new Slice(IntPtr.Zero, 0), 0);
+                var nextSegment = (SliceSegment) segment.Next;
+                segment.SetNext(null);
+                segment = nextSegment;
+            }
+            populatedSegmentCount = 0;
+        }
+
+        // Represents a segment in ReadOnlySequence
+        // Segment is backed by Slice and the instances are reusable.
+        private class SliceSegment : ReadOnlySequenceSegment<byte>
+        {
+            readonly SliceMemoryManager pointerMemoryManager = new SliceMemoryManager();
+
+            public void Reset(Slice slice, long runningIndex)
+            {
+                pointerMemoryManager.Reset(slice);
+                Memory = pointerMemoryManager.Memory;  // maybe not always necessary
+                RunningIndex = runningIndex;
+            }
+
+            public void SetNext(ReadOnlySequenceSegment<byte> next)
+            {
+                Next = next;
+            }        
+        }
+
+        // Allow creating instances of Memory<byte> from Slice.
+        // Represents a chunk of native memory, but doesn't manage its lifetime.
+        // Instances of this class are reuseable - they can be reset to point to a different memory chunk.
+        // That is important to make the instances cacheable (rather then creating new instances
+        // the old ones will be reused to reduce GC pressure).
+        private class SliceMemoryManager : MemoryManager<byte>
+        {
+            private Slice slice;
+
+            public void Reset(Slice slice)
+            {
+                this.slice = slice;
+            }
+
+            public void Reset()
+            {
+                Reset(new Slice(IntPtr.Zero, 0));
+            }
+
+            public override Span<byte> GetSpan()
+            {
+                return slice.ToSpanUnsafe();
+            }
+
+            public override MemoryHandle Pin(int elementIndex = 0)
+            {
+                throw new NotSupportedException();
+            }
+
+            public override void Unpin()
+            {
+            }
+
+            protected override void Dispose(bool disposing)
+            {
+                // NOP
+            }
+        }
+    }
+}
+#endif
diff --git a/src/csharp/Grpc.Core/Internal/Slice.cs b/src/csharp/Grpc.Core/Internal/Slice.cs
new file mode 100644
index 0000000..22eb953
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/Slice.cs
@@ -0,0 +1,68 @@
+#region Copyright notice and license
+
+// Copyright 2019 The 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.
+
+#endregion
+
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal
+{
+    /// <summary>
+    /// Slice of native memory.
+    /// Rough equivalent of grpc_slice (but doesn't support inlined slices, just a pointer to data and length)
+    /// </summary>
+    internal struct Slice
+    {
+        private readonly IntPtr dataPtr;
+        private readonly int length;
+     
+        public Slice(IntPtr dataPtr, int length)
+        {
+            this.dataPtr = dataPtr;
+            this.length = length;
+        }
+
+        public int Length => length;
+
+        // copies data of the slice to given span.
+        // there needs to be enough space in the destination buffer
+        public void CopyTo(ArraySegment<byte> destination)
+        {
+            Marshal.Copy(dataPtr, destination.Array, destination.Offset, length);
+        }
+
+#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY
+        public Span<byte> ToSpanUnsafe()
+        {
+            unsafe
+            {
+                return new Span<byte>((byte*) dataPtr, length);
+            }
+        }
+#endif
+
+        /// <summary>
+        /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Internal.Slice"/>.
+        /// </summary>
+        public override string ToString()
+        {
+            return string.Format("[Slice: dataPtr={0}, length={1}]", dataPtr, length);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/VerifyPeerContext.cs b/src/csharp/Grpc.Core/VerifyPeerContext.cs
new file mode 100644
index 0000000..b1dc60f
--- /dev/null
+++ b/src/csharp/Grpc.Core/VerifyPeerContext.cs
@@ -0,0 +1,48 @@
+#region Copyright notice and license
+
+// Copyright 2019 The 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.
+
+#endregion
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Verification context for VerifyPeerCallback.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// </summary>
+    public class VerifyPeerContext
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="T:Grpc.Core.VerifyPeerContext"/> class.
+        /// </summary>
+        /// <param name="targetName">The target name of the peer.</param>
+        /// <param name="peerPem">The PEM encoded certificate of the peer.</param>
+        internal VerifyPeerContext(string targetName, string peerPem)
+        {
+            this.TargetName = targetName;
+            this.PeerPem = peerPem;
+        }
+
+        /// <summary>
+        /// The target name of the peer.
+        /// </summary>
+        public string TargetName { get; }
+
+        /// <summary>
+        /// The PEM encoded certificate of the peer.
+        /// </summary>
+        public string PeerPem { get; }
+    }
+}
diff --git a/src/csharp/Grpc.Examples/MathGrpc.cs b/src/csharp/Grpc.Examples/MathGrpc.cs
index ba6824d..fab6435 100644
--- a/src/csharp/Grpc.Examples/MathGrpc.cs
+++ b/src/csharp/Grpc.Examples/MathGrpc.cs
@@ -67,6 +67,7 @@
     }
 
     /// <summary>Base class for server-side implementations of Math</summary>
+    [grpc::BindServiceMethod(typeof(Math), "BindService")]
     public abstract partial class MathBase
     {
       /// <summary>
diff --git a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
index 3edec5a..7137b90 100644
--- a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
+++ b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
@@ -54,6 +54,7 @@
     }
 
     /// <summary>Base class for server-side implementations of Health</summary>
+    [grpc::BindServiceMethod(typeof(Health), "BindService")]
     public abstract partial class HealthBase
     {
       /// <summary>
diff --git a/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceGrpc.cs b/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceGrpc.cs
index 09691d2..5b37b14 100644
--- a/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceGrpc.cs
@@ -74,6 +74,7 @@
     }
 
     /// <summary>Base class for server-side implementations of BenchmarkService</summary>
+    [grpc::BindServiceMethod(typeof(BenchmarkService), "BindService")]
     public abstract partial class BenchmarkServiceBase
     {
       /// <summary>
diff --git a/src/csharp/Grpc.IntegrationTesting/EmptyServiceGrpc.cs b/src/csharp/Grpc.IntegrationTesting/EmptyServiceGrpc.cs
index bfa3348..50c6e15 100644
--- a/src/csharp/Grpc.IntegrationTesting/EmptyServiceGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/EmptyServiceGrpc.cs
@@ -39,6 +39,7 @@
     }
 
     /// <summary>Base class for server-side implementations of EmptyService</summary>
+    [grpc::BindServiceMethod(typeof(EmptyService), "BindService")]
     public abstract partial class EmptyServiceBase
     {
     }
diff --git a/src/csharp/Grpc.IntegrationTesting/MetricsGrpc.cs b/src/csharp/Grpc.IntegrationTesting/MetricsGrpc.cs
index 27746c0..9b11e99 100644
--- a/src/csharp/Grpc.IntegrationTesting/MetricsGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/MetricsGrpc.cs
@@ -58,6 +58,7 @@
     }
 
     /// <summary>Base class for server-side implementations of MetricsService</summary>
+    [grpc::BindServiceMethod(typeof(MetricsService), "BindService")]
     public abstract partial class MetricsServiceBase
     {
       /// <summary>
diff --git a/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioServiceGrpc.cs b/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioServiceGrpc.cs
index f92ae8e..1a505eb 100644
--- a/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioServiceGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/ReportQpsScenarioServiceGrpc.cs
@@ -46,6 +46,7 @@
     }
 
     /// <summary>Base class for server-side implementations of ReportQpsScenarioService</summary>
+    [grpc::BindServiceMethod(typeof(ReportQpsScenarioService), "BindService")]
     public abstract partial class ReportQpsScenarioServiceBase
     {
       /// <summary>
diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
index b3c47c2..4c11893 100644
--- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
@@ -46,7 +46,8 @@
         KeyCertificatePair keyCertPair;
 
         public void InitClientAndServer(bool clientAddKeyCertPair,
-                SslClientCertificateRequestType clientCertRequestType)
+                SslClientCertificateRequestType clientCertRequestType,
+                VerifyPeerCallback verifyPeerCallback = null)
         {
             rootCert = File.ReadAllText(TestCredentials.ClientCertAuthorityPath);
             keyCertPair = new KeyCertificatePair(
@@ -54,7 +55,7 @@
                 File.ReadAllText(TestCredentials.ServerPrivateKeyPath));
 
             var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, clientCertRequestType);
-            var clientCredentials = clientAddKeyCertPair ? new SslCredentials(rootCert, keyCertPair) : new SslCredentials(rootCert);
+            var clientCredentials = new SslCredentials(rootCert, clientAddKeyCertPair ? keyCertPair : null, verifyPeerCallback);
 
             // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755
             server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
@@ -188,6 +189,52 @@
             Assert.Throws(typeof(ArgumentNullException), () => new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestAndRequireAndVerify));
         }
 
+        [Test]
+        public async Task VerifyPeerCallback_Accepted()
+        {
+            string targetNameFromCallback = null;
+            string peerPemFromCallback = null;
+            InitClientAndServer(
+                clientAddKeyCertPair: false,
+                clientCertRequestType: SslClientCertificateRequestType.DontRequest,
+                verifyPeerCallback: (ctx) =>
+                {
+                    targetNameFromCallback = ctx.TargetName;
+                    peerPemFromCallback = ctx.PeerPem;
+                    return true;
+                });
+            await CheckAccepted(expectPeerAuthenticated: false);
+            Assert.AreEqual(TestCredentials.DefaultHostOverride, targetNameFromCallback);
+            var expectedServerPem = File.ReadAllText(TestCredentials.ServerCertChainPath).Replace("\r", "");
+            Assert.AreEqual(expectedServerPem, peerPemFromCallback);
+        }
+
+        [Test]
+        public void VerifyPeerCallback_CallbackThrows_Rejected()
+        {
+            InitClientAndServer(
+                clientAddKeyCertPair: false,
+                clientCertRequestType: SslClientCertificateRequestType.DontRequest,
+                verifyPeerCallback: (ctx) =>
+                {
+                    throw new Exception("VerifyPeerCallback has thrown on purpose.");
+                });
+            CheckRejected();
+        }
+
+        [Test]
+        public void VerifyPeerCallback_Rejected()
+        {
+            InitClientAndServer(
+                clientAddKeyCertPair: false,
+                clientCertRequestType: SslClientCertificateRequestType.DontRequest,
+                verifyPeerCallback: (ctx) =>
+                {
+                    return false;
+                });
+            CheckRejected();
+        }
+
         private async Task CheckAccepted(bool expectPeerAuthenticated)
         {
             var call = client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 });
diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
index d47b5fe..e7b9309 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
@@ -105,6 +105,7 @@
     }
 
     /// <summary>Base class for server-side implementations of TestService</summary>
+    [grpc::BindServiceMethod(typeof(TestService), "BindService")]
     public abstract partial class TestServiceBase
     {
       /// <summary>
@@ -580,6 +581,7 @@
     }
 
     /// <summary>Base class for server-side implementations of UnimplementedService</summary>
+    [grpc::BindServiceMethod(typeof(UnimplementedService), "BindService")]
     public abstract partial class UnimplementedServiceBase
     {
       /// <summary>
@@ -719,6 +721,7 @@
     }
 
     /// <summary>Base class for server-side implementations of ReconnectService</summary>
+    [grpc::BindServiceMethod(typeof(ReconnectService), "BindService")]
     public abstract partial class ReconnectServiceBase
     {
       public virtual global::System.Threading.Tasks.Task<global::Grpc.Testing.Empty> Start(global::Grpc.Testing.ReconnectParams request, grpc::ServerCallContext context)
diff --git a/src/csharp/Grpc.IntegrationTesting/WorkerServiceGrpc.cs b/src/csharp/Grpc.IntegrationTesting/WorkerServiceGrpc.cs
index f7dd2ee..14c26f9 100644
--- a/src/csharp/Grpc.IntegrationTesting/WorkerServiceGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/WorkerServiceGrpc.cs
@@ -72,6 +72,7 @@
     }
 
     /// <summary>Base class for server-side implementations of WorkerService</summary>
+    [grpc::BindServiceMethod(typeof(WorkerService), "BindService")]
     public abstract partial class WorkerServiceBase
     {
       /// <summary>
diff --git a/src/csharp/Grpc.Reflection/ReflectionGrpc.cs b/src/csharp/Grpc.Reflection/ReflectionGrpc.cs
index 5007382..f97b314 100644
--- a/src/csharp/Grpc.Reflection/ReflectionGrpc.cs
+++ b/src/csharp/Grpc.Reflection/ReflectionGrpc.cs
@@ -46,6 +46,7 @@
     }
 
     /// <summary>Base class for server-side implementations of ServerReflection</summary>
+    [grpc::BindServiceMethod(typeof(ServerReflection), "BindService")]
     public abstract partial class ServerReflectionBase
     {
       /// <summary>
diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets
index 1a86233..b1030ba 100644
--- a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets
+++ b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets
@@ -28,6 +28,7 @@
       <ProtoRoot Condition="'%(Protobuf.ProtoRoot)' == '' " />
       <CompileOutputs Condition="'%(Protobuf.CompileOutputs)' == ''">True</CompileOutputs>
       <OutputDir Condition="'%(Protobuf.OutputDir)' == '' ">$(Protobuf_OutputPath)</OutputDir>
+      <Generator Condition="'%(Protobuf.Generator)' == '' and '$(DisableProtobufDesignTimeBuild)' != 'true' " >MSBuild:Compile</Generator>
     </Protobuf>
   </ItemDefinitionGroup>
 
diff --git a/src/csharp/README.md b/src/csharp/README.md
index 291772f..c01cae0 100644
--- a/src/csharp/README.md
+++ b/src/csharp/README.md
@@ -40,6 +40,17 @@
 
 See [Experimentally supported platforms](experimental) for instructions.
 
+NUGET DEVELOPMENT FEED (NIGHTLY BUILDS)
+--------------
+
+In production, you should use officially released stable packages available on http://nuget.org, but if you want to test the newest upstream bug fixes and features early, you can can use the development nuget feed where new nuget builds are uploaded nightly.
+
+Feed URL (NuGet v2): https://grpc.jfrog.io/grpc/api/nuget/grpc-nuget-dev
+
+Feed URL (NuGet v3): https://grpc.jfrog.io/grpc/api/nuget/v3/grpc-nuget-dev
+
+The same development nuget packages and packages for other languages can also be found at https://packages.grpc.io/
+
 BUILD FROM SOURCE
 -----------------
 
diff --git a/src/csharp/build/dependencies.props b/src/csharp/build/dependencies.props
index 17fb31e..b018aac 100644
--- a/src/csharp/build/dependencies.props
+++ b/src/csharp/build/dependencies.props
@@ -1,7 +1,7 @@
 <!-- This file is generated -->
 <Project>
   <PropertyGroup>
-    <GrpcCsharpVersion>1.20.1</GrpcCsharpVersion>
+    <GrpcCsharpVersion>1.21.0-dev</GrpcCsharpVersion>
     <GoogleProtobufVersion>3.7.0</GoogleProtobufVersion>
   </PropertyGroup>
 </Project>
diff --git a/src/csharp/build_unitypackage.bat b/src/csharp/build_unitypackage.bat
index cd77d55..8c7718f 100644
--- a/src/csharp/build_unitypackage.bat
+++ b/src/csharp/build_unitypackage.bat
@@ -13,7 +13,7 @@
 @rem limitations under the License.
 
 @rem Current package versions
-set VERSION=1.20.1
+set VERSION=1.21.0-dev
 
 @rem Adjust the location of nuget.exe
 set NUGET=C:\nuget\nuget.exe
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index dc690a6..51e498b 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -59,12 +59,16 @@
   } send_status_from_server;
   grpc_metadata_array recv_initial_metadata;
   grpc_byte_buffer* recv_message;
+  grpc_byte_buffer_reader* recv_message_reader;
   struct {
     grpc_metadata_array trailing_metadata;
     grpc_status_code status;
     grpc_slice status_details;
   } recv_status_on_client;
   int recv_close_on_server_cancelled;
+
+  /* reserve space for byte_buffer_reader */
+  grpc_byte_buffer_reader reserved_recv_message_reader;
 } grpcsharp_batch_context;
 
 GPR_EXPORT grpcsharp_batch_context* GPR_CALLTYPE
@@ -206,6 +210,9 @@
 
   grpcsharp_metadata_array_destroy_metadata_only(&(ctx->recv_initial_metadata));
 
+  if (ctx->recv_message_reader) {
+    grpc_byte_buffer_reader_destroy(ctx->recv_message_reader);
+  }
   grpc_byte_buffer_destroy(ctx->recv_message);
 
   grpcsharp_metadata_array_destroy_metadata_only(
@@ -264,27 +271,42 @@
 }
 
 /*
- * Copies data from recv_message to a buffer. Fatal error occurs if
- * buffer is too small.
+ * Gets the next slice from recv_message byte buffer.
+ * Returns 1 if a slice was get successfully, 0 if there are no more slices to
+ * read. Set slice_len to the length of the slice and the slice_data_ptr to
+ * point to slice's data. Caller must ensure that the byte buffer being read
+ * from stays alive as long as the data of the slice are being accessed
+ * (grpc_byte_buffer_reader_peek method is used internally)
+ *
+ * Remarks:
+ * Slices can only be iterated once.
+ * Initializes recv_message_buffer_reader if it was not initialized yet.
  */
-GPR_EXPORT void GPR_CALLTYPE grpcsharp_batch_context_recv_message_to_buffer(
-    const grpcsharp_batch_context* ctx, char* buffer, size_t buffer_len) {
-  grpc_byte_buffer_reader reader;
-  grpc_slice slice;
-  size_t offset = 0;
+GPR_EXPORT int GPR_CALLTYPE
+grpcsharp_batch_context_recv_message_next_slice_peek(
+    grpcsharp_batch_context* ctx, size_t* slice_len, uint8_t** slice_data_ptr) {
+  *slice_len = 0;
+  *slice_data_ptr = NULL;
 
-  GPR_ASSERT(grpc_byte_buffer_reader_init(&reader, ctx->recv_message));
-
-  while (grpc_byte_buffer_reader_next(&reader, &slice)) {
-    size_t len = GRPC_SLICE_LENGTH(slice);
-    GPR_ASSERT(offset + len <= buffer_len);
-    memcpy(buffer + offset, GRPC_SLICE_START_PTR(slice),
-           GRPC_SLICE_LENGTH(slice));
-    offset += len;
-    grpc_slice_unref(slice);
+  if (!ctx->recv_message) {
+    return 0;
   }
 
-  grpc_byte_buffer_reader_destroy(&reader);
+  if (!ctx->recv_message_reader) {
+    ctx->recv_message_reader = &ctx->reserved_recv_message_reader;
+    GPR_ASSERT(grpc_byte_buffer_reader_init(ctx->recv_message_reader,
+                                            ctx->recv_message));
+  }
+
+  grpc_slice* slice_ptr;
+  if (!grpc_byte_buffer_reader_peek(ctx->recv_message_reader, &slice_ptr)) {
+    return 0;
+  }
+
+  /* recv_message buffer must not be deleted before all the data is read */
+  *slice_len = GRPC_SLICE_LENGTH(*slice_ptr);
+  *slice_data_ptr = GRPC_SLICE_START_PTR(*slice_ptr);
+  return 1;
 }
 
 GPR_EXPORT grpc_status_code GPR_CALLTYPE
@@ -901,6 +923,21 @@
                                   &(ctx->request_metadata), cq, cq, ctx);
 }
 
+/* Native callback dispatcher */
+
+typedef int(GPR_CALLTYPE* grpcsharp_native_callback_dispatcher_func)(
+    void* tag, void* arg0, void* arg1, void* arg2, void* arg3, void* arg4,
+    void* arg5);
+
+static grpcsharp_native_callback_dispatcher_func native_callback_dispatcher =
+    NULL;
+
+GPR_EXPORT void GPR_CALLTYPE grpcsharp_native_callback_dispatcher_init(
+    grpcsharp_native_callback_dispatcher_func func) {
+  GPR_ASSERT(func);
+  native_callback_dispatcher = func;
+}
+
 /* Security */
 
 static char* default_pem_root_certs = NULL;
@@ -927,21 +964,47 @@
   grpc_set_ssl_roots_override_callback(override_ssl_roots_handler);
 }
 
+static void grpcsharp_verify_peer_destroy_handler(void* userdata) {
+  native_callback_dispatcher(userdata, NULL, NULL, (void*)1, NULL, NULL, NULL);
+}
+
+static int grpcsharp_verify_peer_handler(const char* target_name,
+                                         const char* peer_pem, void* userdata) {
+  return native_callback_dispatcher(userdata, (void*)target_name,
+                                    (void*)peer_pem, (void*)0, NULL, NULL,
+                                    NULL);
+}
+
 GPR_EXPORT grpc_channel_credentials* GPR_CALLTYPE
 grpcsharp_ssl_credentials_create(const char* pem_root_certs,
                                  const char* key_cert_pair_cert_chain,
-                                 const char* key_cert_pair_private_key) {
+                                 const char* key_cert_pair_private_key,
+                                 void* verify_peer_callback_tag) {
   grpc_ssl_pem_key_cert_pair key_cert_pair;
+  verify_peer_options verify_options;
+  grpc_ssl_pem_key_cert_pair* key_cert_pair_ptr = NULL;
+  verify_peer_options* verify_options_ptr = NULL;
+
   if (key_cert_pair_cert_chain || key_cert_pair_private_key) {
+    memset(&key_cert_pair, 0, sizeof(key_cert_pair));
     key_cert_pair.cert_chain = key_cert_pair_cert_chain;
     key_cert_pair.private_key = key_cert_pair_private_key;
-    return grpc_ssl_credentials_create(pem_root_certs, &key_cert_pair, NULL,
-                                       NULL);
+    key_cert_pair_ptr = &key_cert_pair;
   } else {
     GPR_ASSERT(!key_cert_pair_cert_chain);
     GPR_ASSERT(!key_cert_pair_private_key);
-    return grpc_ssl_credentials_create(pem_root_certs, NULL, NULL, NULL);
   }
+
+  if (verify_peer_callback_tag != NULL) {
+    memset(&verify_options, 0, sizeof(verify_peer_options));
+    verify_options.verify_peer_callback_userdata = verify_peer_callback_tag;
+    verify_options.verify_peer_destruct = grpcsharp_verify_peer_destroy_handler;
+    verify_options.verify_peer_callback = grpcsharp_verify_peer_handler;
+    verify_options_ptr = &verify_options;
+  }
+
+  return grpc_ssl_credentials_create(pem_root_certs, key_cert_pair_ptr,
+                                     verify_options_ptr, NULL);
 }
 
 GPR_EXPORT void GPR_CALLTYPE
@@ -1010,21 +1073,6 @@
   return grpc_composite_call_credentials_create(creds1, creds2, NULL);
 }
 
-/* Native callback dispatcher */
-
-typedef int(GPR_CALLTYPE* grpcsharp_native_callback_dispatcher_func)(
-    void* tag, void* arg0, void* arg1, void* arg2, void* arg3, void* arg4,
-    void* arg5);
-
-static grpcsharp_native_callback_dispatcher_func native_callback_dispatcher =
-    NULL;
-
-GPR_EXPORT void GPR_CALLTYPE grpcsharp_native_callback_dispatcher_init(
-    grpcsharp_native_callback_dispatcher_func func) {
-  GPR_ASSERT(func);
-  native_callback_dispatcher = func;
-}
-
 /* Metadata credentials plugin */
 
 GPR_EXPORT void GPR_CALLTYPE grpcsharp_metadata_credentials_notify_from_plugin(
diff --git a/src/csharp/tests.json b/src/csharp/tests.json
index c1e7fc1..cacdb30 100644
--- a/src/csharp/tests.json
+++ b/src/csharp/tests.json
@@ -7,8 +7,12 @@
     "Grpc.Core.Internal.Tests.ChannelArgsSafeHandleTest",
     "Grpc.Core.Internal.Tests.CompletionQueueEventTest",
     "Grpc.Core.Internal.Tests.CompletionQueueSafeHandleTest",
+    "Grpc.Core.Internal.Tests.DefaultDeserializationContextTest",
     "Grpc.Core.Internal.Tests.DefaultObjectPoolTest",
+    "Grpc.Core.Internal.Tests.FakeBufferReaderManagerTest",
     "Grpc.Core.Internal.Tests.MetadataArraySafeHandleTest",
+    "Grpc.Core.Internal.Tests.ReusableSliceBufferTest",
+    "Grpc.Core.Internal.Tests.SliceTest",
     "Grpc.Core.Internal.Tests.TimespecTest",
     "Grpc.Core.Tests.AppDomainUnloadTest",
     "Grpc.Core.Tests.AuthContextTest",
diff --git a/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c b/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c
index 0e9d56f..c2f9d20 100644
--- a/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c
+++ b/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c
@@ -46,7 +46,7 @@
   fprintf(stderr, "Should never reach here");
   abort();
 }
-void grpcsharp_batch_context_recv_message_to_buffer() {
+void grpcsharp_batch_context_recv_message_next_slice_peek() {
   fprintf(stderr, "Should never reach here");
   abort();
 }
diff --git "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec" "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
index 558a163..88b8d29 100644
--- "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
+++ "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
@@ -42,7 +42,7 @@
   # exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
   # before them.
   s.name     = '!ProtoCompiler-gRPCPlugin'
-  v = '1.20.1'
+  v = '1.21.0-dev'
   s.version  = v
   s.summary  = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.'
   s.description = <<-DESC
diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h
index 6669067..a430d5c 100644
--- a/src/objective-c/GRPCClient/GRPCCall.h
+++ b/src/objective-c/GRPCClient/GRPCCall.h
@@ -135,7 +135,8 @@
 
   /**
    * The server is currently unavailable. This is most likely a transient condition and may be
-   * corrected by retrying with a backoff.
+   * corrected by retrying with a backoff. Note that it is not always safe to retry
+   * non-idempotent operations.
    */
   GRPCErrorCodeUnavailable = 14,
 
@@ -183,6 +184,12 @@
 - (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
                                error:(nullable NSError *)error;
 
+/**
+ * Issued when flow control is enabled for the call and a message written with writeData: method of
+ * GRPCCall2 is passed to gRPC core with SEND_MESSAGE operation.
+ */
+- (void)didWriteData;
+
 @end
 
 /**
@@ -264,6 +271,14 @@
 - (void)finish;
 
 /**
+ * Tell gRPC to receive the next N gRPC message from gRPC core.
+ *
+ * This method should only be used when flow control is enabled. When flow control is not enabled,
+ * this method is a no-op.
+ */
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
+/**
  * Get a copy of the original call options.
  */
 @property(readonly, copy) GRPCCallOptions *callOptions;
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index 74a1b47..495f942 100644
--- a/src/objective-c/GRPCClient/GRPCCall.m
+++ b/src/objective-c/GRPCClient/GRPCCall.m
@@ -63,6 +63,15 @@
               requestsWriter:(GRXWriter *)requestsWriter
                  callOptions:(GRPCCallOptions *)callOptions;
 
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+                  callSafety:(GRPCCallSafety)safety
+              requestsWriter:(GRXWriter *)requestsWriter
+                 callOptions:(GRPCCallOptions *)callOptions
+                   writeDone:(void (^)(void))writeDone;
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
 @end
 
 @implementation GRPCRequestOptions
@@ -113,6 +122,8 @@
   BOOL _canceled;
   /** Flags whether call has been finished. */
   BOOL _finished;
+  /** The number of pending messages receiving requests. */
+  NSUInteger _pendingReceiveNextMessages;
 }
 
 - (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
@@ -190,10 +201,22 @@
                                       path:_requestOptions.path
                                 callSafety:_requestOptions.safety
                             requestsWriter:_pipe
-                               callOptions:_callOptions];
+                               callOptions:_callOptions
+                                 writeDone:^{
+                                   @synchronized(self) {
+                                     if (self->_handler) {
+                                       [self issueDidWriteData];
+                                     }
+                                   }
+                                 }];
+    [_call setResponseDispatchQueue:_dispatchQueue];
     if (_callOptions.initialMetadata) {
       [_call.requestHeaders addEntriesFromDictionary:_callOptions.initialMetadata];
     }
+    if (_pendingReceiveNextMessages > 0) {
+      [_call receiveNextMessages:_pendingReceiveNextMessages];
+      _pendingReceiveNextMessages = 0;
+    }
     copiedCall = _call;
   }
 
@@ -363,6 +386,33 @@
   }
 }
 
+- (void)issueDidWriteData {
+  @synchronized(self) {
+    if (_callOptions.flowControlEnabled && [_handler respondsToSelector:@selector(didWriteData)]) {
+      dispatch_async(_dispatchQueue, ^{
+        id<GRPCResponseHandler> copiedHandler = nil;
+        @synchronized(self) {
+          copiedHandler = self->_handler;
+        };
+        [copiedHandler didWriteData];
+      });
+    }
+  }
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  // branching based on _callOptions.flowControlEnabled is handled inside _call
+  GRPCCall *copiedCall = nil;
+  @synchronized(self) {
+    copiedCall = _call;
+    if (copiedCall == nil) {
+      _pendingReceiveNextMessages += numberOfMessages;
+      return;
+    }
+  }
+  [copiedCall receiveNextMessages:numberOfMessages];
+}
+
 @end
 
 // The following methods of a C gRPC call object aren't reentrant, and thus
@@ -426,6 +476,15 @@
 
   // The OAuth2 token fetched from a token provider.
   NSString *_fetchedOauth2AccessToken;
+
+  // The callback to be called when a write message op is done.
+  void (^_writeDone)(void);
+
+  // Indicate a read request to core is pending.
+  BOOL _pendingCoreRead;
+
+  // Indicate pending read message request from user.
+  NSUInteger _pendingReceiveNextMessages;
 }
 
 @synthesize state = _state;
@@ -434,6 +493,9 @@
   // Guarantees the code in {} block is invoked only once. See ref at:
   // https://developer.apple.com/documentation/objectivec/nsobject/1418639-initialize?language=objc
   if (self == [GRPCCall self]) {
+    // Enable CFStream by default by do not overwrite if the user explicitly disables CFStream with
+    // environment variable "grpc_cfstream=0"
+    setenv(kCFStreamVarName, "1", 0);
     grpc_init();
     callFlags = [NSMutableDictionary dictionary];
   }
@@ -482,12 +544,26 @@
 - (instancetype)initWithHost:(NSString *)host
                         path:(NSString *)path
                   callSafety:(GRPCCallSafety)safety
-              requestsWriter:(GRXWriter *)requestWriter
+              requestsWriter:(GRXWriter *)requestsWriter
                  callOptions:(GRPCCallOptions *)callOptions {
+  return [self initWithHost:host
+                       path:path
+                 callSafety:safety
+             requestsWriter:requestsWriter
+                callOptions:callOptions
+                  writeDone:nil];
+}
+
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+                  callSafety:(GRPCCallSafety)safety
+              requestsWriter:(GRXWriter *)requestsWriter
+                 callOptions:(GRPCCallOptions *)callOptions
+                   writeDone:(void (^)(void))writeDone {
   // Purposely using pointer rather than length (host.length == 0) for backwards compatibility.
   NSAssert(host != nil && path != nil, @"Neither host nor path can be nil.");
   NSAssert(safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
-  NSAssert(requestWriter.state == GRXWriterStateNotStarted,
+  NSAssert(requestsWriter.state == GRXWriterStateNotStarted,
            @"The requests writer can't be already started.");
   if (!host || !path) {
     return nil;
@@ -495,7 +571,7 @@
   if (safety > GRPCCallSafetyCacheableRequest) {
     return nil;
   }
-  if (requestWriter.state != GRXWriterStateNotStarted) {
+  if (requestsWriter.state != GRXWriterStateNotStarted) {
     return nil;
   }
 
@@ -508,16 +584,20 @@
     // Serial queue to invoke the non-reentrant methods of the grpc_call object.
     _callQueue = dispatch_queue_create("io.grpc.call", DISPATCH_QUEUE_SERIAL);
 
-    _requestWriter = requestWriter;
-
+    _requestWriter = requestsWriter;
     _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
+    _writeDone = writeDone;
 
-    if ([requestWriter isKindOfClass:[GRXImmediateSingleWriter class]]) {
+    if ([requestsWriter isKindOfClass:[GRXImmediateSingleWriter class]]) {
       _unaryCall = YES;
       _unaryOpBatch = [NSMutableArray arrayWithCapacity:kMaxClientBatch];
     }
 
     _responseQueue = dispatch_get_main_queue();
+
+    // do not start a read until initial metadata is received
+    _pendingReceiveNextMessages = 0;
+    _pendingCoreRead = YES;
   }
   return self;
 }
@@ -589,11 +669,16 @@
 // If the call is currently paused, this is a noop. Restarting the call will invoke this
 // method.
 // TODO(jcanizales): Rename to readResponseIfNotPaused.
-- (void)startNextRead {
+- (void)maybeStartNextRead {
   @synchronized(self) {
     if (_state != GRXWriterStateStarted) {
       return;
     }
+    if (_callOptions.flowControlEnabled && (_pendingCoreRead || _pendingReceiveNextMessages == 0)) {
+      return;
+    }
+    _pendingCoreRead = YES;
+    _pendingReceiveNextMessages--;
   }
 
   dispatch_async(_callQueue, ^{
@@ -616,6 +701,7 @@
         // that's on the hands of any server to have. Instead we finish and ask
         // the server to cancel.
         @synchronized(strongSelf) {
+          strongSelf->_pendingCoreRead = NO;
           [strongSelf
               finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
                                                   code:GRPCErrorCodeResourceExhausted
@@ -631,7 +717,13 @@
         @synchronized(strongSelf) {
           [strongSelf->_responseWriteable enqueueValue:data
                                      completionHandler:^{
-                                       [strongSelf startNextRead];
+                                       __strong GRPCCall *strongSelf = weakSelf;
+                                       if (strongSelf) {
+                                         @synchronized(strongSelf) {
+                                           strongSelf->_pendingCoreRead = NO;
+                                           [strongSelf maybeStartNextRead];
+                                         }
+                                       }
                                      }];
         }
       }
@@ -682,6 +774,20 @@
   });
 }
 
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  if (numberOfMessages == 0) {
+    return;
+  }
+  @synchronized(self) {
+    _pendingReceiveNextMessages += numberOfMessages;
+
+    if (_state != GRXWriterStateStarted || !_callOptions.flowControlEnabled) {
+      return;
+    }
+    [self maybeStartNextRead];
+  }
+}
+
 #pragma mark GRXWriteable implementation
 
 // Only called from the call queue. The error handler will be called from the
@@ -695,9 +801,11 @@
     GRPCCall *strongSelf = weakSelf;
     if (strongSelf) {
       strongSelf->_requestWriter.state = GRXWriterStateStarted;
+      if (strongSelf->_writeDone) {
+        strongSelf->_writeDone();
+      }
     }
   };
-
   GRPCOpSendMessage *op =
       [[GRPCOpSendMessage alloc] initWithMessage:message handler:resumingHandler];
   if (!_unaryCall) {
@@ -774,8 +882,11 @@
     // Response headers received.
     __strong GRPCCall *strongSelf = weakSelf;
     if (strongSelf) {
-      strongSelf.responseHeaders = headers;
-      [strongSelf startNextRead];
+      @synchronized(strongSelf) {
+        strongSelf.responseHeaders = headers;
+        strongSelf->_pendingCoreRead = NO;
+        [strongSelf maybeStartNextRead];
+      }
     }
   }
       completionHandler:^(NSError *error, NSDictionary *trailers) {
@@ -929,7 +1040,7 @@
       case GRXWriterStateStarted:
         if (_state == GRXWriterStatePaused) {
           _state = newState;
-          [self startNextRead];
+          [self maybeStartNextRead];
         }
         return;
       case GRXWriterStateNotStarted:
diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.h b/src/objective-c/GRPCClient/GRPCCallOptions.h
index b5bf4c9..9f77652 100644
--- a/src/objective-c/GRPCClient/GRPCCallOptions.h
+++ b/src/objective-c/GRPCClient/GRPCCallOptions.h
@@ -90,6 +90,14 @@
  */
 @property(readonly) NSTimeInterval timeout;
 
+/**
+ * Enable flow control of a gRPC call. The option is default to NO. If set to YES, writeData: method
+ * should only be called at most once before a didWriteData callback is issued, and
+ * receiveNextMessage: must be called each time before gRPC call issues a didReceiveMessage
+ * callback.
+ */
+@property(readonly) BOOL flowControlEnabled;
+
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 
 /**
@@ -232,6 +240,19 @@
  */
 @property(readwrite) NSTimeInterval timeout;
 
+/**
+ * Enable flow control of a gRPC call. The option is default to NO. If set to YES, writeData: method
+ * should only be called at most once before a didWriteData callback is issued, and
+ * receiveNextMessage: must be called each time before gRPC call can issue a didReceiveMessage
+ * callback.
+ *
+ * If writeData: method is called more than once before issuance of a didWriteData callback, gRPC
+ * will continue to queue the message and write them to gRPC core in order. However, the user
+ * assumes their own responsibility of flow control by keeping tracking of the pending writes in
+ * the call.
+ */
+@property(readwrite) BOOL flowControlEnabled;
+
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 
 /**
diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.m b/src/objective-c/GRPCClient/GRPCCallOptions.m
index e59a812..e576641 100644
--- a/src/objective-c/GRPCClient/GRPCCallOptions.m
+++ b/src/objective-c/GRPCClient/GRPCCallOptions.m
@@ -22,6 +22,7 @@
 // The default values for the call options.
 static NSString *const kDefaultServerAuthority = nil;
 static const NSTimeInterval kDefaultTimeout = 0;
+static const BOOL kDefaultFlowControlEnabled = NO;
 static NSDictionary *const kDefaultInitialMetadata = nil;
 static NSString *const kDefaultUserAgentPrefix = nil;
 static const NSUInteger kDefaultResponseSizeLimit = 0;
@@ -59,6 +60,7 @@
  @protected
   NSString *_serverAuthority;
   NSTimeInterval _timeout;
+  BOOL _flowControlEnabled;
   NSString *_oauth2AccessToken;
   id<GRPCAuthorizationProtocol> _authTokenProvider;
   NSDictionary *_initialMetadata;
@@ -84,6 +86,7 @@
 
 @synthesize serverAuthority = _serverAuthority;
 @synthesize timeout = _timeout;
+@synthesize flowControlEnabled = _flowControlEnabled;
 @synthesize oauth2AccessToken = _oauth2AccessToken;
 @synthesize authTokenProvider = _authTokenProvider;
 @synthesize initialMetadata = _initialMetadata;
@@ -109,6 +112,7 @@
 - (instancetype)init {
   return [self initWithServerAuthority:kDefaultServerAuthority
                                timeout:kDefaultTimeout
+                    flowControlEnabled:kDefaultFlowControlEnabled
                      oauth2AccessToken:kDefaultOauth2AccessToken
                      authTokenProvider:kDefaultAuthTokenProvider
                        initialMetadata:kDefaultInitialMetadata
@@ -134,6 +138,7 @@
 
 - (instancetype)initWithServerAuthority:(NSString *)serverAuthority
                                 timeout:(NSTimeInterval)timeout
+                     flowControlEnabled:(BOOL)flowControlEnabled
                       oauth2AccessToken:(NSString *)oauth2AccessToken
                       authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider
                         initialMetadata:(NSDictionary *)initialMetadata
@@ -158,6 +163,7 @@
   if ((self = [super init])) {
     _serverAuthority = [serverAuthority copy];
     _timeout = timeout < 0 ? 0 : timeout;
+    _flowControlEnabled = flowControlEnabled;
     _oauth2AccessToken = [oauth2AccessToken copy];
     _authTokenProvider = authTokenProvider;
     _initialMetadata =
@@ -193,6 +199,7 @@
   GRPCCallOptions *newOptions =
       [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
                                                             timeout:_timeout
+                                                 flowControlEnabled:_flowControlEnabled
                                                   oauth2AccessToken:_oauth2AccessToken
                                                   authTokenProvider:_authTokenProvider
                                                     initialMetadata:_initialMetadata
@@ -221,6 +228,7 @@
   GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone]
       initWithServerAuthority:[_serverAuthority copy]
                       timeout:_timeout
+           flowControlEnabled:_flowControlEnabled
             oauth2AccessToken:[_oauth2AccessToken copy]
             authTokenProvider:_authTokenProvider
               initialMetadata:[[NSDictionary alloc] initWithDictionary:_initialMetadata
@@ -301,6 +309,7 @@
 
 @dynamic serverAuthority;
 @dynamic timeout;
+@dynamic flowControlEnabled;
 @dynamic oauth2AccessToken;
 @dynamic authTokenProvider;
 @dynamic initialMetadata;
@@ -326,6 +335,7 @@
 - (instancetype)init {
   return [self initWithServerAuthority:kDefaultServerAuthority
                                timeout:kDefaultTimeout
+                    flowControlEnabled:kDefaultFlowControlEnabled
                      oauth2AccessToken:kDefaultOauth2AccessToken
                      authTokenProvider:kDefaultAuthTokenProvider
                        initialMetadata:kDefaultInitialMetadata
@@ -353,6 +363,7 @@
   GRPCCallOptions *newOptions =
       [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
                                                             timeout:_timeout
+                                                 flowControlEnabled:_flowControlEnabled
                                                   oauth2AccessToken:_oauth2AccessToken
                                                   authTokenProvider:_authTokenProvider
                                                     initialMetadata:_initialMetadata
@@ -381,6 +392,7 @@
   GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone]
       initWithServerAuthority:_serverAuthority
                       timeout:_timeout
+           flowControlEnabled:_flowControlEnabled
             oauth2AccessToken:_oauth2AccessToken
             authTokenProvider:_authTokenProvider
               initialMetadata:_initialMetadata
@@ -417,6 +429,10 @@
   }
 }
 
+- (void)setFlowControlEnabled:(BOOL)flowControlEnabled {
+  _flowControlEnabled = flowControlEnabled;
+}
+
 - (void)setOauth2AccessToken:(NSString *)oauth2AccessToken {
   _oauth2AccessToken = [oauth2AccessToken copy];
 }
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
index b1a6797..e6522d7 100644
--- a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
+++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
@@ -54,12 +54,14 @@
                                error:(NSError **)errorPtr {
   static dispatch_once_t loading;
   dispatch_once(&loading, ^{
-    NSString *defaultPath = @"gRPCCertificates.bundle/roots";  // .pem
+    NSString *rootsPEM = @"roots";
+    NSString *resourceBundlePath = @"gRPCCertificates.bundle";  // .pem
     // Do not use NSBundle.mainBundle, as it's nil for tests of library projects.
     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
-    NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
-    setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR,
-           [path cStringUsingEncoding:NSUTF8StringEncoding], 1);
+    NSBundle *resourceBundle = [NSBundle
+        bundleWithURL:[[bundle resourceURL] URLByAppendingPathComponent:resourceBundlePath]];
+    NSString *path = [resourceBundle pathForResource:rootsPEM ofType:@"pem"];
+    setenv("GRPC_DEFAULT_SSL_ROOTS_FILE_PATH", [path cStringUsingEncoding:NSUTF8StringEncoding], 1);
   });
 
   NSData *rootsASCII = nil;
diff --git a/src/objective-c/GRPCClient/private/version.h b/src/objective-c/GRPCClient/private/version.h
index 2f62440..fcbdfb2 100644
--- a/src/objective-c/GRPCClient/private/version.h
+++ b/src/objective-c/GRPCClient/private/version.h
@@ -22,4 +22,4 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.20.1"
+#define GRPC_OBJC_VERSION_STRING @"1.21.0-dev"
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h
index 8ce3421..12db46a 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.h
+++ b/src/objective-c/ProtoRPC/ProtoRPC.h
@@ -57,6 +57,13 @@
 - (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
                                error:(nullable NSError *)error;
 
+/**
+ * Issued when flow control is enabled for the call and a message (written with writeMessage: method
+ * of GRPCStreamingProtoCall or the initializer of GRPCUnaryProtoCall) is passed to gRPC core with
+ * SEND_MESSAGE operation.
+ */
+- (void)didWriteMessage;
+
 @end
 
 /** A unary-request RPC call with Protobuf. */
@@ -130,6 +137,26 @@
  */
 - (void)finish;
 
+/**
+ * Tell gRPC to receive another message.
+ *
+ * This method should only be used when flow control is enabled. If flow control is enabled, gRPC
+ * will only receive additional messages after the user indicates so by using either
+ * receiveNextMessage: or receiveNextMessages: methods. If flow control is not enabled, messages
+ * will be automatically received after the previous one is delivered.
+ */
+- (void)receiveNextMessage;
+
+/**
+ * Tell gRPC to receive another N message.
+ *
+ * This method should only be used when flow control is enabled. If flow control is enabled, the
+ * messages received from the server are buffered in gRPC until the user want to receive the next
+ * message. If flow control is not enabled, messages will be automatically received after the
+ * previous one is delivered.
+ */
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m
index 0ab96a5..80988be 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.m
+++ b/src/objective-c/ProtoRPC/ProtoRPC.m
@@ -72,6 +72,7 @@
 
 - (void)start {
   [_call start];
+  [_call receiveNextMessage];
   [_call writeMessage:_message];
   [_call finish];
 }
@@ -197,6 +198,17 @@
   [copiedCall finish];
 }
 
+- (void)receiveNextMessage {
+  [self receiveNextMessages:1];
+}
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  GRPCCall2 *copiedCall;
+  @synchronized(self) {
+    copiedCall = _call;
+  }
+  [copiedCall receiveNextMessages:numberOfMessages];
+}
+
 - (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
   @synchronized(self) {
     if (initialMetadata != nil &&
@@ -260,6 +272,20 @@
   }
 }
 
+- (void)didWriteData {
+  @synchronized(self) {
+    if ([_handler respondsToSelector:@selector(didWriteMessage)]) {
+      dispatch_async(_dispatchQueue, ^{
+        id<GRPCProtoResponseHandler> copiedHandler = nil;
+        @synchronized(self) {
+          copiedHandler = self->_handler;
+        }
+        [copiedHandler didWriteMessage];
+      });
+    }
+  }
+}
+
 - (dispatch_queue_t)dispatchQueue {
   return _dispatchQueue;
 }
diff --git a/src/objective-c/README-CFSTREAM.md b/src/objective-c/README-CFSTREAM.md
index 0cb25ab..184d567 100644
--- a/src/objective-c/README-CFSTREAM.md
+++ b/src/objective-c/README-CFSTREAM.md
@@ -6,24 +6,19 @@
 (see the [doc](https://github.com/grpc/grpc/blob/master/src/objective-c/NetworkTransitionBehavior.md)
 for more information).
 
-CFStream integration is now in experimental state. You will need explicit opt-in to use it to get
+<s>CFStream integration is now in experimental state. You will need explicit opt-in to use it to get
 the benefits of resolving the issues above. We expect to make CFStream the default networking
-interface that gRPC uses when it is ready for production.
+interface that gRPC uses when it is ready for production.</s>
+
+As of v1.21.0, CFStream integration is now the default networking stack being used by gRPC
+Objective-C on iOS layer. You get to use it automatically without special configuration needed. See
+below on how to disable CFStream in case of problem.
 
 ## Usage
-If you use gRPC following the instructions in
-[README.md](https://github.com/grpc/grpc/blob/master/src/objective-c/README.md):
-- Replace the
-dependency on `gRPC-ProtoRPC` with `gRPC-ProtoRPC/CFStream`.
-- Enable CFStream with environment variable `grpc_cfstream=1`. This can be done either in Xcode
-  console or by your code with `setenv()` before gRPC is initialized.
-
-If your project directly depends on podspecs other than `gRPC-ProtoRPC` (e.g. `gRPC` or
-`gRPC-Core`):
-
-- Make your projects depend on subspecs corresponding to CFStream in each gRPC podspec.
-- Enable CFStream with environment variable `grpc_cfstream=1`. This can be done either in Xcode
-  console or by your code with `setenv()` before gRPC is initialized.
+If you use gRPC Objective-C library on iOS, CFStream is on automatically. If you use it on other
+platforms, you can turn it on with macro `GRPC_CFSTREAM=1` for the pod 'gRPC-Core' and 'gRPC'. In
+case of problem and you want to disable CFStream on iOS, you can set environment variable
+"grpc\_cfstream=0".
 
 ## Notes
 
diff --git a/src/objective-c/manual_tests/GrpcIosTest.xcodeproj/xcshareddata/xcschemes/GrpcIosTest.xcscheme b/src/objective-c/manual_tests/GrpcIosTest.xcodeproj/xcshareddata/xcschemes/GrpcIosTest.xcscheme
new file mode 100644
index 0000000..b9fb9d4
--- /dev/null
+++ b/src/objective-c/manual_tests/GrpcIosTest.xcodeproj/xcshareddata/xcschemes/GrpcIosTest.xcscheme
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1010"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "5EDA907A220DF0BC0046D27A"
+               BuildableName = "GrpcIosTest.app"
+               BlueprintName = "GrpcIosTest"
+               ReferencedContainer = "container:GrpcIosTest.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "B0C18CA3222DEF140002B502"
+               BuildableName = "GrpcIosTestUITests.xctest"
+               BlueprintName = "GrpcIosTestUITests"
+               ReferencedContainer = "container:GrpcIosTest.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "5EDA907A220DF0BC0046D27A"
+            BuildableName = "GrpcIosTest.app"
+            BlueprintName = "GrpcIosTest"
+            ReferencedContainer = "container:GrpcIosTest.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "5EDA907A220DF0BC0046D27A"
+            BuildableName = "GrpcIosTest.app"
+            BlueprintName = "GrpcIosTest"
+            ReferencedContainer = "container:GrpcIosTest.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "5EDA907A220DF0BC0046D27A"
+            BuildableName = "GrpcIosTest.app"
+            BlueprintName = "GrpcIosTest"
+            ReferencedContainer = "container:GrpcIosTest.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/src/objective-c/manual_tests/GrpcIosTestUITests/GrpcIosTestUITests.m b/src/objective-c/manual_tests/GrpcIosTestUITests/GrpcIosTestUITests.m
index b0a929e..a75b632 100644
--- a/src/objective-c/manual_tests/GrpcIosTestUITests/GrpcIosTestUITests.m
+++ b/src/objective-c/manual_tests/GrpcIosTestUITests/GrpcIosTestUITests.m
@@ -19,6 +19,7 @@
 #import <XCTest/XCTest.h>
 
 NSTimeInterval const kWaitTime = 30;
+int const kNumIterations = 1;
 
 @interface GrpcIosTestUITests : XCTestCase
 @end
@@ -32,16 +33,27 @@
   self.continueAfterFailure = NO;
   [[[XCUIApplication alloc] init] launch];
   testApp = [[XCUIApplication alloc] initWithBundleIdentifier:@"io.grpc.GrpcIosTest"];
+  [testApp activate];
+  // Reset RPC counter
+  [self pressButton:@"Reset counter"];
+
   settingsApp = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.Preferences"];
   [settingsApp activate];
+  [NSThread sleepForTimeInterval:1];
   // Go back to the first page of Settings.
   XCUIElement *backButton = settingsApp.navigationBars.buttons.firstMatch;
-  while (backButton.exists) {
+  while (backButton.exists && backButton.isHittable) {
+    NSLog(@"Tapping back button");
     [backButton tap];
   }
   XCTAssert([settingsApp.navigationBars[@"Settings"] waitForExistenceWithTimeout:kWaitTime]);
+  NSLog(@"Turning off airplane mode");
   // Turn off airplane mode
   [self setAirplaneMode:NO];
+
+  // Turn on wifi
+  NSLog(@"Turning on wifi");
+  [self setWifi:YES];
 }
 
 - (void)tearDown {
@@ -49,14 +61,25 @@
 
 - (void)doUnaryCall {
   [testApp activate];
-  [testApp.buttons[@"Unary call"] tap];
+  [self pressButton:@"Unary call"];
 }
 
-- (void)doStreamingCall {
+- (void)do10UnaryCalls {
   [testApp activate];
-  [testApp.buttons[@"Start streaming call"] tap];
-  [testApp.buttons[@"Send Message"] tap];
-  [testApp.buttons[@"Stop streaming call"] tap];
+  [self pressButton:@"10 Unary calls"];
+}
+
+- (void)pressButton:(NSString *)name {
+  // Wait for button to be visible
+  while (![testApp.buttons[name] exists] || ![testApp.buttons[name] isHittable]) {
+    [NSThread sleepForTimeInterval:1];
+  }
+  // Wait until all events in run loop have been processed
+  while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true) == kCFRunLoopRunHandledSource)
+    ;
+
+  NSLog(@"Pressing button: %@", name);
+  [testApp.buttons[name] tap];
 }
 
 - (void)expectCallSuccess {
@@ -67,45 +90,87 @@
   XCTAssert([testApp.staticTexts[@"Call failed"] waitForExistenceWithTimeout:kWaitTime]);
 }
 
+- (void)expectCallSuccessOrFailed {
+  NSDate *startTime = [NSDate date];
+  while (![testApp.staticTexts[@"Call done"] exists] &&
+         ![testApp.staticTexts[@"Call failed"] exists]) {
+    XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:startTime], kWaitTime);
+    [NSThread sleepForTimeInterval:1];
+  }
+}
+
 - (void)setAirplaneMode:(BOOL)to {
   [settingsApp activate];
   XCUIElement *mySwitch = settingsApp.tables.element.cells.switches[@"Airplane Mode"];
   BOOL from = [(NSString *)mySwitch.value boolValue];
+  NSLog(@"Setting airplane from: %d to: %d", from, to);
   if (from != to) {
     [mySwitch tap];
-    // wait for gRPC to detect the change
-    sleep(10);
+    // wait for network change to finish
+    [NSThread sleepForTimeInterval:5];
   }
   XCTAssert([(NSString *)mySwitch.value boolValue] == to);
 }
+- (void)setWifi:(BOOL)to {
+  [settingsApp activate];
+  [settingsApp.tables.element.cells.staticTexts[@"Wi-Fi"] tap];
+  XCUIElement *wifiSwitch = settingsApp.tables.cells.switches[@"Wi-Fi"];
+  BOOL from = [(NSString *)wifiSwitch.value boolValue];
+  NSLog(@"Setting wifi from: %d to: %d", from, to);
+  if (from != to) {
+    [wifiSwitch tap];
+    // wait for wifi networks to be detected
+    [NSThread sleepForTimeInterval:10];
+  }
+  // Go back to the first page of Settings.
+  XCUIElement *backButton = settingsApp.navigationBars.buttons.firstMatch;
+  [backButton tap];
+}
 
-- (void)testBackgroundBeforeUnaryCall {
+- (int)getRandomNumberBetween:(int)min max:(int)max {
+  return min + arc4random_uniform((max - min + 1));
+}
+
+- (void)testBackgroundBeforeCall {
+  NSLog(@"%s", __func__);
   // Open test app
   [testApp activate];
-
   // Send test app to background
   [XCUIDevice.sharedDevice pressButton:XCUIDeviceButtonHome];
-  sleep(5);
+
+  // Wait a bit
+  int sleepTime = [self getRandomNumberBetween:5 max:10];
+  NSLog(@"Sleeping for %d seconds", sleepTime);
+  [NSThread sleepForTimeInterval:sleepTime];
 
   // Bring test app to foreground and make a unary call. Call should succeed
   [self doUnaryCall];
   [self expectCallSuccess];
 }
 
-- (void)testBackgroundBeforeStreamingCall {
-  // Open test app
+- (void)testBackgroundDuringStreamingCall {
+  NSLog(@"%s", __func__);
+  // Open test app and start a streaming call
   [testApp activate];
+  [self pressButton:@"Start streaming call"];
 
   // Send test app to background
   [XCUIDevice.sharedDevice pressButton:XCUIDeviceButtonHome];
-  sleep(5);
+
+  // Wait a bit
+  int sleepTime = [self getRandomNumberBetween:5 max:10];
+  NSLog(@"Sleeping for %d seconds", sleepTime);
+  [NSThread sleepForTimeInterval:sleepTime];
 
   // Bring test app to foreground and make a streaming call. Call should succeed.
-  [self doStreamingCall];
+  [testApp activate];
+  [self pressButton:@"Send Message"];
+  [self pressButton:@"Stop streaming call"];
   [self expectCallSuccess];
 }
 
-- (void)testUnaryCallAfterNetworkFlap {
+- (void)testCallAfterNetworkFlap {
+  NSLog(@"%s", __func__);
   // Open test app and make a unary call. Channel to server should be open after this.
   [self doUnaryCall];
   [self expectCallSuccess];
@@ -119,26 +184,16 @@
   [self expectCallSuccess];
 }
 
-- (void)testStreamingCallAfterNetworkFlap {
-  // Open test app and make a unary call. Channel to server should be open after this.
-  [self doUnaryCall];
-  [self expectCallSuccess];
-
-  // Toggle airplane mode on and off
-  [self setAirplaneMode:YES];
-  [self setAirplaneMode:NO];
-
-  [self doStreamingCall];
-  [self expectCallSuccess];
-}
-
-- (void)testUnaryCallWhileNetworkDown {
+- (void)testCallWhileNetworkDown {
+  NSLog(@"%s", __func__);
   // Open test app and make a unary call. Channel to server should be open after this.
   [self doUnaryCall];
   [self expectCallSuccess];
 
   // Turn on airplane mode
   [self setAirplaneMode:YES];
+  // Turn off wifi
+  [self setWifi:NO];
 
   // Unary call should fail
   [self doUnaryCall];
@@ -146,29 +201,131 @@
 
   // Turn off airplane mode
   [self setAirplaneMode:NO];
+  // Turn on wifi
+  [self setWifi:YES];
 
   // Unary call should succeed
   [self doUnaryCall];
   [self expectCallSuccess];
 }
 
-- (void)testStreamingCallWhileNetworkDown {
+- (void)testSwitchApp {
+  NSLog(@"%s", __func__);
   // Open test app and make a unary call. Channel to server should be open after this.
   [self doUnaryCall];
   [self expectCallSuccess];
 
-  // Turn on airplane mode
-  [self setAirplaneMode:YES];
+  // Send test app to background
+  [XCUIDevice.sharedDevice pressButton:XCUIDeviceButtonHome];
 
-  // Streaming call should fail
-  [self doStreamingCall];
-  [self expectCallFailed];
+  // Open stocks app
+  XCUIApplication *stocksApp =
+      [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.stocks"];
+  [stocksApp activate];
+  // Ensure that stocks app is running in the foreground
+  XCTAssert([stocksApp waitForState:XCUIApplicationStateRunningForeground timeout:5]);
+  // Wait a bit
+  int sleepTime = [self getRandomNumberBetween:5 max:10];
+  NSLog(@"Sleeping for %d seconds", sleepTime);
+  [NSThread sleepForTimeInterval:sleepTime];
+  [stocksApp terminate];
 
-  // Turn off airplane mode
-  [self setAirplaneMode:NO];
-
-  // Unary call should succeed
-  [self doStreamingCall];
+  // Make another unary call
+  [self doUnaryCall];
   [self expectCallSuccess];
 }
+
+- (void)testNetworkFlapDuringStreamingCall {
+  NSLog(@"%s", __func__);
+  // Open test app and make a unary call. Channel to server should be open after this.
+  [self doUnaryCall];
+  [self expectCallSuccess];
+  // Start streaming call and send a message
+  [self pressButton:@"Start streaming call"];
+  [self pressButton:@"Send Message"];
+
+  // Toggle network on and off
+  [self setAirplaneMode:YES];
+  [self setWifi:NO];
+  [self setAirplaneMode:NO];
+  [self setWifi:YES];
+
+  [testApp activate];
+  [self pressButton:@"Stop streaming call"];
+  // The call will fail if the stream gets a read error, else the call will succeed.
+  [self expectCallSuccessOrFailed];
+
+  // Make another unary call, it should succeed
+  [self doUnaryCall];
+  [self expectCallSuccess];
+}
+
+- (void)testConcurrentCalls {
+  NSLog(@"%s", __func__);
+
+  // Press button to start 10 unary calls
+  [self do10UnaryCalls];
+
+  // Toggle airplane mode on and off
+  [self setAirplaneMode:YES];
+  [self setAirplaneMode:NO];
+
+  // 10 calls should have completed
+  [testApp activate];
+  XCTAssert([testApp.staticTexts[@"Calls completed: 10"] waitForExistenceWithTimeout:kWaitTime]);
+}
+
+- (void)invokeTest {
+  for (int i = 0; i < kNumIterations; i++) {
+    [super invokeTest];
+  }
+}
+
+- (void)testUnaryCallTurnOffWifi {
+  NSLog(@"%s", __func__);
+  // Open test app and make a unary call. Channel to server should be open after this.
+  [self doUnaryCall];
+  [self expectCallSuccess];
+
+  // Turn off wifi
+  [self setWifi:NO];
+
+  // Phone should switch to cellular connection, call should succeed
+  [self doUnaryCall];
+  [self expectCallSuccess];
+
+  // Turn on wifi
+  [self setWifi:YES];
+
+  // Call should succeed after turning wifi back on
+  [self doUnaryCall];
+  [self expectCallSuccess];
+}
+
+- (void)testStreamingCallTurnOffWifi {
+  NSLog(@"%s", __func__);
+  // Open test app and make a unary call. Channel to server should be open after this.
+  [self doUnaryCall];
+  [self expectCallSuccess];
+
+  // Start streaming call and send a message
+  [self pressButton:@"Start streaming call"];
+  [self pressButton:@"Send Message"];
+
+  // Turn off wifi
+  [self setWifi:NO];
+
+  // Phone should switch to cellular connection, this results in the call failing
+  [testApp activate];
+  [self pressButton:@"Stop streaming call"];
+  [self expectCallFailed];
+
+  // Turn on wifi
+  [self setWifi:YES];
+
+  // Call should succeed after turning wifi back on
+  [self doUnaryCall];
+  [self expectCallSuccess];
+}
+
 @end
diff --git a/src/objective-c/manual_tests/Main.storyboard b/src/objective-c/manual_tests/Main.storyboard
index e7e0530..4090841 100644
--- a/src/objective-c/manual_tests/Main.storyboard
+++ b/src/objective-c/manual_tests/Main.storyboard
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
     <device id="retina4_7" orientation="portrait">
         <adaptation id="fullscreen"/>
     </device>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -26,16 +26,8 @@
                                     <action selector="tapStreamingCallStart:" destination="BYZ-38-t0r" eventType="touchUpInside" id="6tF-9u-Gew"/>
                                 </connections>
                             </button>
-                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dxG-ib-Xoa">
-                                <rect key="frame" x="197" y="221" width="132" height="30"/>
-                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                                <state key="normal" title="Stop streaming call"/>
-                                <connections>
-                                    <action selector="tapStreamingCallStop:" destination="BYZ-38-t0r" eventType="touchUpInside" id="QLQ-yw-wlZ"/>
-                                </connections>
-                            </button>
                             <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ay7-K7-Lez">
-                                <rect key="frame" x="48" y="71" width="69" height="30"/>
+                                <rect key="frame" x="35" y="71" width="69" height="30"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                 <state key="normal" title="Unary call"/>
                                 <connections>
@@ -43,13 +35,51 @@
                                 </connections>
                             </button>
                             <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Sq5-O5-fiK">
-                                <rect key="frame" x="213" y="147" width="101" height="30"/>
+                                <rect key="frame" x="196" y="147" width="101" height="30"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                 <state key="normal" title="Send Message"/>
                                 <connections>
                                     <action selector="tapStreamingCallSend:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Qz0-PB-akd"/>
                                 </connections>
                             </button>
+                            <label opaque="NO" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7JI-bR-X8r">
+                                <rect key="frame" x="134" y="323" width="98" height="56"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Calls completed: 0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Wwu-kV-w7R">
+                                <rect key="frame" x="128" y="418" width="180" height="56"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="q8d-Mi-SAJ">
+                                <rect key="frame" x="35" y="147" width="96" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <state key="normal" title="10 Unary calls"/>
+                                <connections>
+                                    <action selector="tap10UnaryCalls:" destination="BYZ-38-t0r" eventType="touchUpInside" id="pY2-Rq-hVn"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Csm-o1-8eh">
+                                <rect key="frame" x="35" y="221" width="96" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <state key="normal" title="Reset counter"/>
+                                <connections>
+                                    <action selector="resetCounter:" destination="BYZ-38-t0r" eventType="touchUpInside" id="InY-SX-R6H"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dxG-ib-Xoa">
+                                <rect key="frame" x="196" y="221" width="132" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <state key="normal" title="Stop streaming call"/>
+                                <connections>
+                                    <action selector="tapStreamingCallStop:" destination="BYZ-38-t0r" eventType="touchUpInside" id="QLQ-yw-wlZ"/>
+                                </connections>
+                            </button>
                         </subviews>
                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                         <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
@@ -57,6 +87,7 @@
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
             </objects>
+            <point key="canvasLocation" x="128.80000000000001" y="132.68365817091455"/>
         </scene>
     </scenes>
 </document>
diff --git a/src/objective-c/manual_tests/ViewController.m b/src/objective-c/manual_tests/ViewController.m
index 813b176..50a729e 100644
--- a/src/objective-c/manual_tests/ViewController.m
+++ b/src/objective-c/manual_tests/ViewController.m
@@ -27,33 +27,36 @@
 const int32_t kMessageSize = 100;
 
 @interface ViewController : UIViewController<GRPCProtoResponseHandler>
-@property(strong, nonatomic) UILabel *fromLabel;
+@property(strong, nonatomic) UILabel *callStatusLabel;
+@property(strong, nonatomic) UILabel *callCountLabel;
 @end
 
 @implementation ViewController {
   RMTTestService *_service;
   dispatch_queue_t _dispatchQueue;
   GRPCStreamingProtoCall *_call;
+  int _calls_completed;
 }
 - (instancetype)init {
   self = [super init];
+  _calls_completed = 0;
   return self;
 }
 
 - (void)viewDidLoad {
   [super viewDidLoad];
   _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
-  _fromLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 500, 200, 20)];
-  _fromLabel.textColor = [UIColor blueColor];
-  _fromLabel.backgroundColor = [UIColor whiteColor];
-  [self.view addSubview:_fromLabel];
+  _callStatusLabel = (UILabel *)[self.view viewWithTag:1];
+  _callCountLabel = (UILabel *)[self.view viewWithTag:2];
 }
 
-- (IBAction)tapUnaryCall:(id)sender {
+- (void)startUnaryCall {
   if (_service == nil) {
     _service = [RMTTestService serviceWithHost:kRemoteHost];
   }
-  self->_fromLabel.text = @"";
+  dispatch_async(dispatch_get_main_queue(), ^{
+    self->_callStatusLabel.text = @"";
+  });
 
   // Set up request proto message
   RMTSimpleRequest *request = [RMTSimpleRequest message];
@@ -63,14 +66,43 @@
 
   GRPCUnaryProtoCall *call =
       [_service unaryCallWithMessage:request responseHandler:self callOptions:nil];
+
   [call start];
 }
 
+- (IBAction)tapUnaryCall:(id)sender {
+  NSLog(@"In tapUnaryCall");
+  [self startUnaryCall];
+}
+
+- (IBAction)tap10UnaryCalls:(id)sender {
+  NSLog(@"In tap10UnaryCalls");
+  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+    // Background thread
+    for (int i = 0; i < 10; ++i) {
+      [self startUnaryCall];
+      [NSThread sleepForTimeInterval:0.5];
+    }
+  });
+}
+
+- (IBAction)resetCounter:(id)sender {
+  _calls_completed = 0;
+  dispatch_async(dispatch_get_main_queue(), ^{
+    self->_callCountLabel.text =
+        [NSString stringWithFormat:@"Calls completed: %d", self->_calls_completed];
+    self->_callStatusLabel.text = @"";
+  });
+}
+
 - (IBAction)tapStreamingCallStart:(id)sender {
+  NSLog(@"In tapStreamingCallStart");
   if (_service == nil) {
     _service = [RMTTestService serviceWithHost:kRemoteHost];
   }
-  self->_fromLabel.text = @"";
+  dispatch_async(dispatch_get_main_queue(), ^{
+    self->_callStatusLabel.text = @"";
+  });
 
   // Set up request proto message
   RMTStreamingOutputCallRequest *request = RMTStreamingOutputCallRequest.message;
@@ -83,10 +115,10 @@
   [call start];
   _call = call;
   // display something to confirm the tester the call is started
-  NSLog(@"Started streaming call");
 }
 
 - (IBAction)tapStreamingCallSend:(id)sender {
+  NSLog(@"In tapStreamingCallSend");
   if (_call == nil) return;
 
   RMTStreamingOutputCallRequest *request = RMTStreamingOutputCallRequest.message;
@@ -99,6 +131,7 @@
 }
 
 - (IBAction)tapStreamingCallStop:(id)sender {
+  NSLog(@"In tapStreamingCallStop");
   if (_call == nil) return;
 
   [_call finish];
@@ -118,13 +151,18 @@
   NSLog(@"Recv trailing metadata: %@, error: %@", trailingMetadata, error);
   if (error == nil) {
     dispatch_async(dispatch_get_main_queue(), ^{
-      self->_fromLabel.text = @"Call done";
+      self->_callStatusLabel.text = @"Call done";
     });
   } else {
     dispatch_async(dispatch_get_main_queue(), ^{
-      self->_fromLabel.text = @"Call failed";
+      self->_callStatusLabel.text = @"Call failed";
     });
   }
+  ++_calls_completed;
+  dispatch_async(dispatch_get_main_queue(), ^{
+    self->_callCountLabel.text =
+        [NSString stringWithFormat:@"Calls completed: %d", self->_calls_completed];
+  });
 }
 
 - (dispatch_queue_t)dispatchQueue {
diff --git a/src/objective-c/tests/APIv2Tests/APIv2Tests.m b/src/objective-c/tests/APIv2Tests/APIv2Tests.m
index ca7bf47..db29375 100644
--- a/src/objective-c/tests/APIv2Tests/APIv2Tests.m
+++ b/src/objective-c/tests/APIv2Tests/APIv2Tests.m
@@ -22,6 +22,7 @@
 #import <XCTest/XCTest.h>
 
 #include <grpc/grpc.h>
+#include <grpc/support/port_platform.h>
 
 #import "../version.h"
 
@@ -39,11 +40,13 @@
 static GRPCProtoMethod *kInexistentMethod;
 static GRPCProtoMethod *kEmptyCallMethod;
 static GRPCProtoMethod *kUnaryCallMethod;
+static GRPCProtoMethod *kOutputStreamingCallMethod;
 static GRPCProtoMethod *kFullDuplexCallMethod;
 
 static const int kSimpleDataLength = 100;
 
-static const NSTimeInterval kTestTimeout = 16;
+static const NSTimeInterval kTestTimeout = 8;
+static const NSTimeInterval kInvertedTimeout = 2;
 
 // Reveal the _class ivar for testing access
 @interface GRPCCall2 () {
@@ -58,6 +61,11 @@
 
 - (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
                                 messageCallback:(void (^)(id))messageCallback
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
+                              writeDataCallback:(void (^)(void))writeDataCallback;
+
+- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
+                                messageCallback:(void (^)(id))messageCallback
                                   closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback;
 
 @end
@@ -66,21 +74,33 @@
   void (^_initialMetadataCallback)(NSDictionary *);
   void (^_messageCallback)(id);
   void (^_closeCallback)(NSDictionary *, NSError *);
+  void (^_writeDataCallback)(void);
   dispatch_queue_t _dispatchQueue;
 }
 
 - (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
                                 messageCallback:(void (^)(id))messageCallback
-                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
+                              writeDataCallback:(void (^)(void))writeDataCallback {
   if ((self = [super init])) {
     _initialMetadataCallback = initialMetadataCallback;
     _messageCallback = messageCallback;
     _closeCallback = closeCallback;
+    _writeDataCallback = writeDataCallback;
     _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
   }
   return self;
 }
 
+- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
+                                messageCallback:(void (^)(id))messageCallback
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
+  return [self initWithInitialMetadataCallback:initialMetadataCallback
+                               messageCallback:messageCallback
+                                 closeCallback:closeCallback
+                             writeDataCallback:nil];
+}
+
 - (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
   if (self->_initialMetadataCallback) {
     self->_initialMetadataCallback(initialMetadata);
@@ -99,6 +119,12 @@
   }
 }
 
+- (void)didWriteData {
+  if (self->_writeDataCallback) {
+    self->_writeDataCallback();
+  }
+}
+
 - (dispatch_queue_t)dispatchQueue {
   return _dispatchQueue;
 }
@@ -119,6 +145,9 @@
       [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"];
   kUnaryCallMethod =
       [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"];
+  kOutputStreamingCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage
+                                                                service:kService
+                                                                 method:@"StreamingOutputCall"];
   kFullDuplexCallMethod =
       [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"];
 }
@@ -198,7 +227,9 @@
                expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"];
                expectedUserAgent =
                    [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING];
-               expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "];
+               expectedUserAgent = [expectedUserAgent stringByAppendingString:@" ("];
+               expectedUserAgent = [expectedUserAgent stringByAppendingString:@GPR_PLATFORM_STRING];
+               expectedUserAgent = [expectedUserAgent stringByAppendingString:@"; chttp2; "];
                expectedUserAgent = [expectedUserAgent
                    stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]];
                expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"];
@@ -475,4 +506,268 @@
   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
 }
 
+- (void)testFlowControlWrite {
+  __weak XCTestExpectation *expectWriteData =
+      [self expectationWithDescription:@"Reported write data"];
+
+  RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message];
+  RMTResponseParameters *parameters = [RMTResponseParameters message];
+  parameters.size = kSimpleDataLength;
+  [request.responseParametersArray addObject:parameters];
+  request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
+
+  GRPCRequestOptions *callRequest =
+      [[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
+                                          path:kUnaryCallMethod.HTTPPath
+                                        safety:GRPCCallSafetyDefault];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+  options.flowControlEnabled = YES;
+  GRPCCall2 *call =
+      [[GRPCCall2 alloc] initWithRequestOptions:callRequest
+                                responseHandler:[[ClientTestsBlockCallbacks alloc]
+                                                    initWithInitialMetadataCallback:nil
+                                                                    messageCallback:nil
+                                                                      closeCallback:nil
+                                                                  writeDataCallback:^{
+                                                                    [expectWriteData fulfill];
+                                                                  }]
+                                    callOptions:options];
+
+  [call start];
+  [call receiveNextMessages:1];
+  [call writeData:[request data]];
+
+  // Wait for 3 seconds and make sure we do not receive the response
+  [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+
+  [call finish];
+}
+
+- (void)testFlowControlRead {
+  __weak __block XCTestExpectation *expectBlockedMessage =
+      [self expectationWithDescription:@"Message not delivered without recvNextMessage"];
+  __weak __block XCTestExpectation *expectPassedMessage = nil;
+  __weak __block XCTestExpectation *expectBlockedClose =
+      [self expectationWithDescription:@"Call not closed with pending message"];
+  __weak __block XCTestExpectation *expectPassedClose = nil;
+  expectBlockedMessage.inverted = YES;
+  expectBlockedClose.inverted = YES;
+
+  RMTSimpleRequest *request = [RMTSimpleRequest message];
+  request.responseSize = kSimpleDataLength;
+  request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
+
+  GRPCRequestOptions *callRequest =
+      [[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
+                                          path:kUnaryCallMethod.HTTPPath
+                                        safety:GRPCCallSafetyDefault];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+  options.flowControlEnabled = YES;
+  __block int unblocked = NO;
+  GRPCCall2 *call = [[GRPCCall2 alloc]
+      initWithRequestOptions:callRequest
+             responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                                 messageCallback:^(NSData *message) {
+                                   if (!unblocked) {
+                                     [expectBlockedMessage fulfill];
+                                   } else {
+                                     [expectPassedMessage fulfill];
+                                   }
+                                 }
+                                 closeCallback:^(NSDictionary *trailers, NSError *error) {
+                                   if (!unblocked) {
+                                     [expectBlockedClose fulfill];
+                                   } else {
+                                     [expectPassedClose fulfill];
+                                   }
+                                 }]
+                 callOptions:options];
+
+  [call start];
+  [call writeData:[request data]];
+  [call finish];
+
+  // Wait to make sure we do not receive the response
+  [self waitForExpectationsWithTimeout:kInvertedTimeout handler:nil];
+
+  expectPassedMessage =
+      [self expectationWithDescription:@"Message delivered with receiveNextMessage"];
+  expectPassedClose = [self expectationWithDescription:@"Close delivered after receiveNextMessage"];
+
+  unblocked = YES;
+  [call receiveNextMessages:1];
+
+  [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+- (void)testFlowControlMultipleMessages {
+  __weak XCTestExpectation *expectPassedMessage =
+      [self expectationWithDescription:@"two messages delivered with receiveNextMessage"];
+  expectPassedMessage.expectedFulfillmentCount = 2;
+  __weak XCTestExpectation *expectBlockedMessage =
+      [self expectationWithDescription:@"Message 3 not delivered"];
+  expectBlockedMessage.inverted = YES;
+  __weak XCTestExpectation *expectWriteTwice =
+      [self expectationWithDescription:@"Write 2 messages done"];
+  expectWriteTwice.expectedFulfillmentCount = 2;
+
+  RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message];
+  RMTResponseParameters *parameters = [RMTResponseParameters message];
+  parameters.size = kSimpleDataLength;
+  [request.responseParametersArray addObject:parameters];
+  request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
+
+  GRPCRequestOptions *callRequest =
+      [[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
+                                          path:kFullDuplexCallMethod.HTTPPath
+                                        safety:GRPCCallSafetyDefault];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+  options.flowControlEnabled = YES;
+  __block NSUInteger messageId = 0;
+  __block GRPCCall2 *call = [[GRPCCall2 alloc]
+      initWithRequestOptions:callRequest
+             responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                                 messageCallback:^(NSData *message) {
+                                   if (messageId <= 1) {
+                                     [expectPassedMessage fulfill];
+                                   } else {
+                                     [expectBlockedMessage fulfill];
+                                   }
+                                   messageId++;
+                                 }
+                                 closeCallback:nil
+                                 writeDataCallback:^{
+                                   [expectWriteTwice fulfill];
+                                 }]
+                 callOptions:options];
+
+  [call receiveNextMessages:2];
+  [call start];
+  [call writeData:[request data]];
+  [call writeData:[request data]];
+
+  [self waitForExpectationsWithTimeout:kInvertedTimeout handler:nil];
+}
+
+- (void)testFlowControlReadReadyBeforeStart {
+  __weak XCTestExpectation *expectPassedMessage =
+      [self expectationWithDescription:@"Message delivered with receiveNextMessage"];
+  __weak XCTestExpectation *expectPassedClose =
+      [self expectationWithDescription:@"Close delivered with receiveNextMessage"];
+
+  RMTSimpleRequest *request = [RMTSimpleRequest message];
+  request.responseSize = kSimpleDataLength;
+  request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
+
+  GRPCRequestOptions *callRequest =
+      [[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
+                                          path:kUnaryCallMethod.HTTPPath
+                                        safety:GRPCCallSafetyDefault];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+  options.flowControlEnabled = YES;
+  __block BOOL closed = NO;
+  GRPCCall2 *call = [[GRPCCall2 alloc]
+      initWithRequestOptions:callRequest
+             responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                                 messageCallback:^(NSData *message) {
+                                   [expectPassedMessage fulfill];
+                                   XCTAssertFalse(closed);
+                                 }
+                                 closeCallback:^(NSDictionary *ttrailers, NSError *error) {
+                                   closed = YES;
+                                   [expectPassedClose fulfill];
+                                 }]
+                 callOptions:options];
+
+  [call receiveNextMessages:1];
+  [call start];
+  [call writeData:[request data]];
+  [call finish];
+
+  [self waitForExpectationsWithTimeout:kInvertedTimeout handler:nil];
+}
+
+- (void)testFlowControlReadReadyAfterStart {
+  __weak XCTestExpectation *expectPassedMessage =
+      [self expectationWithDescription:@"Message delivered with receiveNextMessage"];
+  __weak XCTestExpectation *expectPassedClose =
+      [self expectationWithDescription:@"Close delivered with receiveNextMessage"];
+
+  RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message];
+  RMTResponseParameters *parameters = [RMTResponseParameters message];
+  parameters.size = kSimpleDataLength;
+  [request.responseParametersArray addObject:parameters];
+  request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
+
+  GRPCRequestOptions *callRequest =
+      [[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
+                                          path:kUnaryCallMethod.HTTPPath
+                                        safety:GRPCCallSafetyDefault];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = GRPCTransportTypeInsecure;
+  options.flowControlEnabled = YES;
+  __block BOOL closed = NO;
+  GRPCCall2 *call = [[GRPCCall2 alloc]
+      initWithRequestOptions:callRequest
+             responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                                 messageCallback:^(NSData *message) {
+                                   [expectPassedMessage fulfill];
+                                   XCTAssertFalse(closed);
+                                 }
+                                 closeCallback:^(NSDictionary *trailers, NSError *error) {
+                                   closed = YES;
+                                   [expectPassedClose fulfill];
+                                 }]
+                 callOptions:options];
+
+  [call start];
+  [call receiveNextMessages:1];
+  [call writeData:[request data]];
+  [call finish];
+
+  [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+- (void)testFlowControlReadNonBlockingFailure {
+  __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
+
+  GRPCRequestOptions *requestOptions =
+      [[GRPCRequestOptions alloc] initWithHost:kHostAddress
+                                          path:kUnaryCallMethod.HTTPPath
+                                        safety:GRPCCallSafetyDefault];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.flowControlEnabled = YES;
+  options.transportType = GRPCTransportTypeInsecure;
+
+  RMTSimpleRequest *request = [RMTSimpleRequest message];
+  request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit];
+
+  RMTEchoStatus *status = [RMTEchoStatus message];
+  status.code = 2;
+  status.message = @"test";
+  request.responseStatus = status;
+
+  GRPCCall2 *call = [[GRPCCall2 alloc]
+      initWithRequestOptions:requestOptions
+             responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                                 messageCallback:^(NSData *data) {
+                                   XCTFail(@"Received unexpected message");
+                                 }
+                                 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
+                                   XCTAssertNotNil(error, @"Expecting non-nil error");
+                                   XCTAssertEqual(error.code, 2);
+                                   [completion fulfill];
+                                 }]
+                 callOptions:options];
+  [call writeData:[request data]];
+  [call start];
+  [call finish];
+
+  [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
 @end
diff --git a/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm b/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm
index 2fac1be..0d081e4 100644
--- a/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm
+++ b/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm
@@ -37,11 +37,11 @@
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
 #include "src/core/lib/security/credentials/credentials.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
@@ -172,7 +172,7 @@
   GPR_ASSERT(roots_file != NULL);
   GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
   fclose(roots_file);
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
 
   grpc_init();
 
diff --git a/src/objective-c/tests/GRPCClientTests.m b/src/objective-c/tests/GRPCClientTests.m
index 3ef0468..b361078 100644
--- a/src/objective-c/tests/GRPCClientTests.m
+++ b/src/objective-c/tests/GRPCClientTests.m
@@ -16,9 +16,9 @@
  *
  */
 
-#import <UIKit/UIKit.h>
 #import <XCTest/XCTest.h>
 #import <grpc/grpc.h>
+#import <grpc/support/port_platform.h>
 
 #import <GRPCClient/GRPCCall+ChannelArg.h>
 #import <GRPCClient/GRPCCall+OAuth2.h>
@@ -297,7 +297,9 @@
         expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING];
         expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"];
         expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING];
-        expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "];
+        expectedUserAgent = [expectedUserAgent stringByAppendingString:@" ("];
+        expectedUserAgent = [expectedUserAgent stringByAppendingString:@GPR_PLATFORM_STRING];
+        expectedUserAgent = [expectedUserAgent stringByAppendingString:@"; chttp2; "];
         expectedUserAgent = [expectedUserAgent
             stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]];
         expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"];
diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m
index 717dfd8..aab38e3 100644
--- a/src/objective-c/tests/InteropTests.m
+++ b/src/objective-c/tests/InteropTests.m
@@ -20,7 +20,9 @@
 
 #include <grpc/status.h>
 
+#ifdef GRPC_COMPILE_WITH_CRONET
 #import <Cronet/Cronet.h>
+#endif
 #import <GRPCClient/GRPCCall+ChannelArg.h>
 #import <GRPCClient/GRPCCall+Cronet.h>
 #import <GRPCClient/GRPCCall+Tests.h>
@@ -79,6 +81,11 @@
 
 - (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
                                 messageCallback:(void (^)(id))messageCallback
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
+                           writeMessageCallback:(void (^)(void))writeMessageCallback;
+
+- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
+                                messageCallback:(void (^)(id))messageCallback
                                   closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback;
 
 @end
@@ -87,21 +94,33 @@
   void (^_initialMetadataCallback)(NSDictionary *);
   void (^_messageCallback)(id);
   void (^_closeCallback)(NSDictionary *, NSError *);
+  void (^_writeMessageCallback)(void);
   dispatch_queue_t _dispatchQueue;
 }
 
 - (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
                                 messageCallback:(void (^)(id))messageCallback
-                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
+                           writeMessageCallback:(void (^)(void))writeMessageCallback {
   if ((self = [super init])) {
     _initialMetadataCallback = initialMetadataCallback;
     _messageCallback = messageCallback;
     _closeCallback = closeCallback;
+    _writeMessageCallback = writeMessageCallback;
     _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
   }
   return self;
 }
 
+- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
+                                messageCallback:(void (^)(id))messageCallback
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
+  return [self initWithInitialMetadataCallback:initialMetadataCallback
+                               messageCallback:messageCallback
+                                 closeCallback:closeCallback
+                          writeMessageCallback:nil];
+}
+
 - (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
   if (_initialMetadataCallback) {
     _initialMetadataCallback(initialMetadata);
@@ -120,6 +139,12 @@
   }
 }
 
+- (void)didWriteMessage {
+  if (_writeMessageCallback) {
+    _writeMessageCallback();
+  }
+}
+
 - (dispatch_queue_t)dispatchQueue {
   return _dispatchQueue;
 }
@@ -227,6 +252,52 @@
   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
 }
 
+// Test that responses can be dispatched even if we do not run main run-loop
+- (void)testAsyncDispatchWithV2API {
+  XCTAssertNotNil([[self class] host]);
+
+  GPBEmpty *request = [GPBEmpty message];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = [[self class] transportType];
+  options.PEMRootCertificates = [[self class] PEMRootCertificates];
+  options.hostNameOverride = [[self class] hostNameOverride];
+
+  __block BOOL messageReceived = NO;
+  __block BOOL done = NO;
+  NSCondition *cond = [[NSCondition alloc] init];
+  GRPCUnaryProtoCall *call = [_service
+      emptyCallWithMessage:request
+           responseHandler:[[InteropTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                               messageCallback:^(id message) {
+                                 if (message) {
+                                   id expectedResponse = [GPBEmpty message];
+                                   XCTAssertEqualObjects(message, expectedResponse);
+                                   [cond lock];
+                                   messageReceived = YES;
+                                   [cond unlock];
+                                 }
+                               }
+                               closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
+                                 XCTAssertNil(error, @"Unexpected error: %@", error);
+                                 [cond lock];
+                                 done = YES;
+                                 [cond signal];
+                                 [cond unlock];
+                               }]
+               callOptions:options];
+
+  NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:TEST_TIMEOUT];
+  [call start];
+
+  [cond lock];
+  while (!done && [deadline timeIntervalSinceNow] > 0) {
+    [cond waitUntilDate:deadline];
+  }
+  XCTAssertTrue(messageReceived);
+  XCTAssertTrue(done);
+  [cond unlock];
+}
+
 - (void)testLargeUnaryRPC {
   XCTAssertNotNil([[self class] host]);
   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
@@ -293,6 +364,90 @@
   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
 }
 
+- (void)testConcurrentRPCsWithErrorsWithV2API {
+  NSMutableArray *completeExpectations = [NSMutableArray array];
+  NSMutableArray *calls = [NSMutableArray array];
+  int num_rpcs = 10;
+  for (int i = 0; i < num_rpcs; ++i) {
+    [completeExpectations
+        addObject:[self expectationWithDescription:
+                            [NSString stringWithFormat:@"Received trailer for RPC %d", i]]];
+
+    RMTSimpleRequest *request = [RMTSimpleRequest message];
+    request.responseType = RMTPayloadType_Compressable;
+    request.responseSize = 314159;
+    request.payload.body = [NSMutableData dataWithLength:271828];
+    if (i % 3 == 0) {
+      request.responseStatus.code = GRPC_STATUS_UNAVAILABLE;
+    } else if (i % 7 == 0) {
+      request.responseStatus.code = GRPC_STATUS_CANCELLED;
+    }
+    GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+    options.transportType = [[self class] transportType];
+    options.PEMRootCertificates = [[self class] PEMRootCertificates];
+    options.hostNameOverride = [[self class] hostNameOverride];
+
+    GRPCUnaryProtoCall *call = [_service
+        unaryCallWithMessage:request
+             responseHandler:[[InteropTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                                 messageCallback:^(id message) {
+                                   if (message) {
+                                     RMTSimpleResponse *expectedResponse =
+                                         [RMTSimpleResponse message];
+                                     expectedResponse.payload.type = RMTPayloadType_Compressable;
+                                     expectedResponse.payload.body =
+                                         [NSMutableData dataWithLength:314159];
+                                     XCTAssertEqualObjects(message, expectedResponse);
+                                   }
+                                 }
+                                 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
+                                   [completeExpectations[i] fulfill];
+                                 }]
+                 callOptions:options];
+    [calls addObject:call];
+  }
+
+  for (int i = 0; i < num_rpcs; ++i) {
+    GRPCUnaryProtoCall *call = calls[i];
+    [call start];
+  }
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+}
+
+- (void)testConcurrentRPCsWithErrors {
+  NSMutableArray *completeExpectations = [NSMutableArray array];
+  int num_rpcs = 10;
+  for (int i = 0; i < num_rpcs; ++i) {
+    [completeExpectations
+        addObject:[self expectationWithDescription:
+                            [NSString stringWithFormat:@"Received trailer for RPC %d", i]]];
+
+    RMTSimpleRequest *request = [RMTSimpleRequest message];
+    request.responseType = RMTPayloadType_Compressable;
+    request.responseSize = 314159;
+    request.payload.body = [NSMutableData dataWithLength:271828];
+    if (i % 3 == 0) {
+      request.responseStatus.code = GRPC_STATUS_UNAVAILABLE;
+    } else if (i % 7 == 0) {
+      request.responseStatus.code = GRPC_STATUS_CANCELLED;
+    }
+
+    [_service unaryCallWithRequest:request
+                           handler:^(RMTSimpleResponse *response, NSError *error) {
+                             if (error == nil) {
+                               RMTSimpleResponse *expectedResponse = [RMTSimpleResponse message];
+                               expectedResponse.payload.type = RMTPayloadType_Compressable;
+                               expectedResponse.payload.body =
+                                   [NSMutableData dataWithLength:314159];
+                               XCTAssertEqualObjects(response, expectedResponse);
+                             }
+                             [completeExpectations[i] fulfill];
+                           }];
+  }
+
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+}
+
 - (void)testPacketCoalescing {
   XCTAssertNotNil([[self class] host]);
   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
@@ -570,6 +725,67 @@
   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
 }
 
+- (void)testPingPongRPCWithFlowControl {
+  XCTAssertNotNil([[self class] host]);
+  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPongWithV2API"];
+
+  NSArray *requests = @[ @27182, @8, @1828, @45904 ];
+  NSArray *responses = @[ @31415, @9, @2653, @58979 ];
+
+  __block int index = 0;
+
+  id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
+                                               requestedResponseSize:responses[index]];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = [[self class] transportType];
+  options.PEMRootCertificates = [[self class] PEMRootCertificates];
+  options.hostNameOverride = [[self class] hostNameOverride];
+  options.flowControlEnabled = YES;
+  __block BOOL canWriteData = NO;
+
+  __block GRPCStreamingProtoCall *call = [_service
+      fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc]
+                                            initWithInitialMetadataCallback:nil
+                                            messageCallback:^(id message) {
+                                              XCTAssertLessThan(index, 4,
+                                                                @"More than 4 responses received.");
+                                              id expected = [RMTStreamingOutputCallResponse
+                                                  messageWithPayloadSize:responses[index]];
+                                              XCTAssertEqualObjects(message, expected);
+                                              index += 1;
+                                              if (index < 4) {
+                                                id request = [RMTStreamingOutputCallRequest
+                                                    messageWithPayloadSize:requests[index]
+                                                     requestedResponseSize:responses[index]];
+                                                XCTAssertTrue(canWriteData);
+                                                canWriteData = NO;
+                                                [call writeMessage:request];
+                                                [call receiveNextMessage];
+                                              } else {
+                                                [call finish];
+                                              }
+                                            }
+                                            closeCallback:^(NSDictionary *trailingMetadata,
+                                                            NSError *error) {
+                                              XCTAssertNil(error,
+                                                           @"Finished with unexpected error: %@",
+                                                           error);
+                                              XCTAssertEqual(index, 4,
+                                                             @"Received %i responses instead of 4.",
+                                                             index);
+                                              [expectation fulfill];
+                                            }
+                                            writeMessageCallback:^{
+                                              canWriteData = YES;
+                                            }]
+                            callOptions:options];
+  [call start];
+  [call receiveNextMessage];
+  [call writeMessage:request];
+
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+}
+
 - (void)testEmptyStreamRPC {
   XCTAssertNotNil([[self class] host]);
   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyStream"];
@@ -753,7 +969,7 @@
                      [GRPCCall closeOpenConnections];
 #pragma clang diagnostic pop
 
-                     [_service
+                     [self->_service
                          emptyCallWithRequest:request
                                       handler:^(GPBEmpty *response, NSError *error) {
                                         XCTAssertNil(
diff --git a/src/objective-c/tests/MacTests/Info.plist b/src/objective-c/tests/MacTests/Info.plist
new file mode 100644
index 0000000..6c40a6c
--- /dev/null
+++ b/src/objective-c/tests/MacTests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>
diff --git a/src/objective-c/tests/MacTests/StressTests.h b/src/objective-c/tests/MacTests/StressTests.h
new file mode 100644
index 0000000..8bee0e6
--- /dev/null
+++ b/src/objective-c/tests/MacTests/StressTests.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * Copyright 2019 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.
+ *
+ */
+
+#import <XCTest/XCTest.h>
+
+#import <GRPCClient/GRPCCallOptions.h>
+
+@interface StressTests : XCTestCase
+/**
+ * Host to send the RPCs to. The base implementation returns nil, which would make all tests to
+ * fail.
+ * Override in a subclass to perform these tests against a specific address.
+ */
++ (NSString *)host;
+
+/**
+ * Bytes of overhead of test proto responses due to encoding. This is used to excercise the behavior
+ * when responses are just above or below the max response size. For some reason, the local and
+ * remote servers enconde responses with different overhead (?), so this is defined per-subclass.
+ */
+- (int32_t)encodingOverhead;
+
+/**
+ * The type of transport to be used. The base implementation returns default. Subclasses should
+ * override to appropriate settings.
+ */
++ (GRPCTransportType)transportType;
+
+/**
+ * The root certificates to be used. The base implementation returns nil. Subclasses should override
+ * to appropriate settings.
+ */
++ (NSString *)PEMRootCertificates;
+
+/**
+ * The root certificates to be used. The base implementation returns nil. Subclasses should override
+ * to appropriate settings.
+ */
++ (NSString *)hostNameOverride;
+
+@end
diff --git a/src/objective-c/tests/MacTests/StressTests.m b/src/objective-c/tests/MacTests/StressTests.m
new file mode 100644
index 0000000..22174b5
--- /dev/null
+++ b/src/objective-c/tests/MacTests/StressTests.m
@@ -0,0 +1,237 @@
+/*
+ *
+ * Copyright 2019 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 "StressTests.h"
+
+#import <GRPCClient/GRPCCall+ChannelArg.h>
+#import <GRPCClient/GRPCCall+Tests.h>
+#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
+#import <ProtoRPC/ProtoRPC.h>
+#import <RemoteTest/Messages.pbobjc.h>
+#import <RemoteTest/Test.pbobjc.h>
+#import <RemoteTest/Test.pbrpc.h>
+#import <RxLibrary/GRXBufferedPipe.h>
+#import <RxLibrary/GRXWriter+Immediate.h>
+#import <grpc/grpc.h>
+#import <grpc/support/log.h>
+
+#define TEST_TIMEOUT 32
+
+extern const char *kCFStreamVarName;
+
+// Convenience class to use blocks as callbacks
+@interface MacTestsBlockCallbacks : NSObject<GRPCProtoResponseHandler>
+
+- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
+                                messageCallback:(void (^)(id))messageCallback
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback;
+
+@end
+
+@implementation MacTestsBlockCallbacks {
+  void (^_initialMetadataCallback)(NSDictionary *);
+  void (^_messageCallback)(id);
+  void (^_closeCallback)(NSDictionary *, NSError *);
+  dispatch_queue_t _dispatchQueue;
+}
+
+- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
+                                messageCallback:(void (^)(id))messageCallback
+                                  closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
+  if ((self = [super init])) {
+    _initialMetadataCallback = initialMetadataCallback;
+    _messageCallback = messageCallback;
+    _closeCallback = closeCallback;
+    _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
+  }
+  return self;
+}
+
+- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
+  if (_initialMetadataCallback) {
+    _initialMetadataCallback(initialMetadata);
+  }
+}
+
+- (void)didReceiveProtoMessage:(GPBMessage *)message {
+  if (_messageCallback) {
+    _messageCallback(message);
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (_closeCallback) {
+    _closeCallback(trailingMetadata, error);
+  }
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return _dispatchQueue;
+}
+
+@end
+
+@implementation StressTests {
+  RMTTestService *_service;
+}
+
++ (NSString *)host {
+  return nil;
+}
+
++ (NSString *)hostAddress {
+  return nil;
+}
+
++ (NSString *)PEMRootCertificates {
+  return nil;
+}
+
++ (NSString *)hostNameOverride {
+  return nil;
+}
+
+- (int32_t)encodingOverhead {
+  return 0;
+}
+
++ (void)setUp {
+  setenv(kCFStreamVarName, "1", 1);
+}
+
+- (void)setUp {
+  self.continueAfterFailure = NO;
+
+  [GRPCCall resetHostSettings];
+
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = [[self class] transportType];
+  options.PEMRootCertificates = [[self class] PEMRootCertificates];
+  options.hostNameOverride = [[self class] hostNameOverride];
+  _service = [RMTTestService serviceWithHost:[[self class] host] callOptions:options];
+  system([[NSString stringWithFormat:@"sudo ifconfig lo0 alias %@", [[self class] hostAddress]]
+      UTF8String]);
+}
+
+- (void)tearDown {
+  system([[NSString stringWithFormat:@"sudo ifconfig lo0 -alias %@", [[self class] hostAddress]]
+      UTF8String]);
+}
+
++ (GRPCTransportType)transportType {
+  return GRPCTransportTypeChttp2BoringSSL;
+}
+
+- (void)testNetworkFlapWithV2API {
+  NSMutableArray *completeExpectations = [NSMutableArray array];
+  NSMutableArray *calls = [NSMutableArray array];
+  int num_rpcs = 100;
+  __block BOOL address_removed = FALSE;
+  __block BOOL address_readded = FALSE;
+  for (int i = 0; i < num_rpcs; ++i) {
+    [completeExpectations
+        addObject:[self expectationWithDescription:
+                            [NSString stringWithFormat:@"Received trailer for RPC %d", i]]];
+
+    RMTSimpleRequest *request = [RMTSimpleRequest message];
+    request.responseType = RMTPayloadType_Compressable;
+    request.responseSize = 314159;
+    request.payload.body = [NSMutableData dataWithLength:271828];
+
+    GRPCUnaryProtoCall *call = [_service
+        unaryCallWithMessage:request
+             responseHandler:[[MacTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+                                 messageCallback:^(id message) {
+                                   if (message) {
+                                     RMTSimpleResponse *expectedResponse =
+                                         [RMTSimpleResponse message];
+                                     expectedResponse.payload.type = RMTPayloadType_Compressable;
+                                     expectedResponse.payload.body =
+                                         [NSMutableData dataWithLength:314159];
+                                     XCTAssertEqualObjects(message, expectedResponse);
+                                   }
+                                 }
+                                 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
+
+                                   @synchronized(self) {
+                                     if (error == nil && !address_removed) {
+                                       system([[NSString
+                                           stringWithFormat:@"sudo ifconfig lo0 -alias %@",
+                                                            [[self class] hostAddress]]
+                                           UTF8String]);
+                                       address_removed = YES;
+                                     } else if (error != nil && !address_readded) {
+                                       system([
+                                           [NSString stringWithFormat:@"sudo ifconfig lo0 alias %@",
+                                                                      [[self class] hostAddress]]
+                                           UTF8String]);
+                                       address_readded = YES;
+                                     }
+                                   }
+                                   [completeExpectations[i] fulfill];
+                                 }]
+                 callOptions:nil];
+    [calls addObject:call];
+  }
+
+  for (int i = 0; i < num_rpcs; ++i) {
+    GRPCUnaryProtoCall *call = calls[i];
+    [call start];
+    [NSThread sleepForTimeInterval:0.1f];
+  }
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+}
+
+- (void)testNetworkFlapWithV1API {
+  NSMutableArray *completeExpectations = [NSMutableArray array];
+  int num_rpcs = 100;
+  __block BOOL address_removed = FALSE;
+  __block BOOL address_readded = FALSE;
+  for (int i = 0; i < num_rpcs; ++i) {
+    [completeExpectations
+        addObject:[self expectationWithDescription:
+                            [NSString stringWithFormat:@"Received response for RPC %d", i]]];
+
+    RMTSimpleRequest *request = [RMTSimpleRequest message];
+    request.responseType = RMTPayloadType_Compressable;
+    request.responseSize = 314159;
+    request.payload.body = [NSMutableData dataWithLength:271828];
+
+    [_service unaryCallWithRequest:request
+                           handler:^(RMTSimpleResponse *response, NSError *error) {
+                             @synchronized(self) {
+                               if (error == nil && !address_removed) {
+                                 system([[NSString stringWithFormat:@"sudo ifconfig lo0 -alias %@",
+                                                                    [[self class] hostAddress]]
+                                     UTF8String]);
+                                 address_removed = YES;
+                               } else if (error != nil && !address_readded) {
+                                 system([[NSString stringWithFormat:@"sudo ifconfig lo0 alias %@",
+                                                                    [[self class] hostAddress]]
+                                     UTF8String]);
+                                 address_readded = YES;
+                               }
+                             }
+
+                             [completeExpectations[i] fulfill];
+                           }];
+
+    [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+  }
+}
+
+@end
diff --git a/src/objective-c/tests/MacTests/StressTestsCleartext.m b/src/objective-c/tests/MacTests/StressTestsCleartext.m
new file mode 100644
index 0000000..2b3a861
--- /dev/null
+++ b/src/objective-c/tests/MacTests/StressTestsCleartext.m
@@ -0,0 +1,68 @@
+
+/*
+ *
+ * Copyright 2019 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.
+ *
+ */
+
+#import <GRPCClient/GRPCCall+Tests.h>
+#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
+
+#import "StressTests.h"
+
+static NSString *const kHostAddress = @"10.0.0.1";
+
+// The Protocol Buffers encoding overhead of local interop server. Acquired
+// by experiment. Adjust this when server's proto file changes.
+static int32_t kLocalInteropServerOverhead = 10;
+
+/** Tests in InteropTests.m, sending the RPCs to a local cleartext server. */
+@interface StressTestsCleartext : StressTests
+@end
+
+@implementation StressTestsCleartext
+
++ (NSString *)host {
+  return [NSString stringWithFormat:@"%@:5050", kHostAddress];
+}
+
++ (NSString *)hostAddress {
+  return kHostAddress;
+}
+
++ (NSString *)PEMRootCertificates {
+  return nil;
+}
+
++ (NSString *)hostNameOverride {
+  return nil;
+}
+
+- (int32_t)encodingOverhead {
+  return kLocalInteropServerOverhead;  // bytes
+}
+
+- (void)setUp {
+  [super setUp];
+
+  // Register test server as non-SSL.
+  [GRPCCall useInsecureConnectionsForHost:[[self class] host]];
+}
+
++ (GRPCTransportType)transportType {
+  return GRPCTransportTypeInsecure;
+}
+
+@end
diff --git a/src/objective-c/tests/MacTests/StressTestsSSL.m b/src/objective-c/tests/MacTests/StressTestsSSL.m
new file mode 100644
index 0000000..c5d79fe
--- /dev/null
+++ b/src/objective-c/tests/MacTests/StressTestsSSL.m
@@ -0,0 +1,71 @@
+/*
+ *
+ * Copyright 2019 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.
+ *
+ */
+
+#import <GRPCClient/GRPCCall+Tests.h>
+#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
+
+#include "StressTests.h"
+
+static NSString *const kHostAddress = @"10.0.0.1";
+// The Protocol Buffers encoding overhead of local interop server. Acquired
+// by experiment. Adjust this when server's proto file changes.
+static int32_t kLocalInteropServerOverhead = 10;
+
+@interface StressTestsSSL : StressTests
+@end
+
+@implementation StressTestsSSL
+
++ (NSString *)host {
+  return [NSString stringWithFormat:@"%@:5051", kHostAddress];
+}
+
++ (NSString *)hostAddress {
+  return kHostAddress;
+}
+
++ (NSString *)PEMRootCertificates {
+  NSBundle *bundle = [NSBundle bundleForClass:[self class]];
+  NSString *certsPath =
+      [bundle pathForResource:@"TestCertificates.bundle/test-certificates" ofType:@"pem"];
+  NSError *error;
+  return [NSString stringWithContentsOfFile:certsPath encoding:NSUTF8StringEncoding error:&error];
+}
+
++ (NSString *)hostNameOverride {
+  return @"foo.test.google.fr";
+}
+
+- (int32_t)encodingOverhead {
+  return kLocalInteropServerOverhead;  // bytes
+}
+
++ (GRPCTransportType)transportType {
+  return GRPCTransportTypeChttp2BoringSSL;
+}
+
+- (void)setUp {
+  [super setUp];
+
+  // Register test server certificates and name.
+  NSBundle *bundle = [NSBundle bundleForClass:[self class]];
+  NSString *certsPath =
+      [bundle pathForResource:@"TestCertificates.bundle/test-certificates" ofType:@"pem"];
+  [GRPCCall useTestCertsPath:certsPath testName:@"foo.test.google.fr" forHost:[[self class] host]];
+}
+@end
diff --git a/src/objective-c/tests/Podfile b/src/objective-c/tests/Podfile
index 8e5f588..34ed668 100644
--- a/src/objective-c/tests/Podfile
+++ b/src/objective-c/tests/Podfile
@@ -1,5 +1,4 @@
 source 'https://github.com/CocoaPods/Specs.git'
-platform :ios, '8.0'
 
 install! 'cocoapods', :deterministic_uuids => false
 
@@ -17,9 +16,13 @@
   InteropTestsMultipleChannels
   InteropTestsCallOptions
   UnitTests
+  InteropTestsRemoteCFStream
+  InteropTestsLocalSSLCFStream
+  InteropTestsLocalCleartextCFStream
   APIv2Tests
 ).each do |target_name|
   target target_name do
+    platform :ios, '8.0'
     pod 'Protobuf', :path => "#{GRPC_LOCAL_SRC}/third_party/protobuf", :inhibit_warnings => true
 
     pod '!ProtoCompiler',            :path => "#{GRPC_LOCAL_SRC}/src/objective-c"
@@ -40,25 +43,20 @@
   end
 end
 
-%w(
-  InteropTestsRemoteCFStream
-  InteropTestsLocalSSLCFStream
-  InteropTestsLocalCleartextCFStream
-).each do |target_name|
-  target target_name do
-    pod 'Protobuf', :path => "#{GRPC_LOCAL_SRC}/third_party/protobuf", :inhibit_warnings => true
+target 'MacTests' do
+  platform :osx, '10.13'
+  pod 'Protobuf', :path => "#{GRPC_LOCAL_SRC}/third_party/protobuf", :inhibit_warnings => true
 
-    pod '!ProtoCompiler',            :path => "#{GRPC_LOCAL_SRC}/src/objective-c"
-    pod '!ProtoCompiler-gRPCPlugin', :path => "#{GRPC_LOCAL_SRC}/src/objective-c"
+  pod '!ProtoCompiler',            :path => "#{GRPC_LOCAL_SRC}/src/objective-c"
+  pod '!ProtoCompiler-gRPCPlugin', :path => "#{GRPC_LOCAL_SRC}/src/objective-c"
 
-    pod 'BoringSSL-GRPC',                 :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c", :inhibit_warnings => true
+  pod 'BoringSSL-GRPC',       :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c", :inhibit_warnings => true
 
-    pod 'gRPC/CFStream',             :path => GRPC_LOCAL_SRC
-    pod 'gRPC-Core/CFStream-Implementation',       :path => GRPC_LOCAL_SRC
-    pod 'gRPC-RxLibrary', :path => GRPC_LOCAL_SRC
-    pod 'gRPC-ProtoRPC',  :path => GRPC_LOCAL_SRC, :inhibit_warnings => true
-    pod 'RemoteTest', :path => "RemoteTestClient", :inhibit_warnings => true
-  end
+  pod 'gRPC',           :path => GRPC_LOCAL_SRC
+  pod 'gRPC-Core',      :path => GRPC_LOCAL_SRC
+  pod 'gRPC-RxLibrary', :path => GRPC_LOCAL_SRC
+  pod 'gRPC-ProtoRPC',  :path => GRPC_LOCAL_SRC, :inhibit_warnings => true
+  pod 'RemoteTest', :path => "RemoteTestClient", :inhibit_warnings => true
 end
 
 %w(
@@ -66,6 +64,7 @@
   CronetUnitTests
 ).each do |target_name|
   target target_name do
+    platform :ios, '8.0'
     pod 'BoringSSL-GRPC', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c", :inhibit_warnings => true
     pod 'CronetFramework', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c"
     pod 'gRPC-Core', :path => GRPC_LOCAL_SRC
@@ -76,6 +75,7 @@
 end
 
 target 'ChannelTests' do
+  platform :ios, '8.0'
   pod 'gRPC', :path => GRPC_LOCAL_SRC
   pod 'gRPC-Core', :path => GRPC_LOCAL_SRC
   pod 'BoringSSL-GRPC', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c", :inhibit_warnings => true
@@ -127,11 +127,7 @@
         # GPR_UNREACHABLE_CODE causes "Control may reach end of non-void
         # function" warning
         config.build_settings['GCC_WARN_ABOUT_RETURN_TYPE'] = 'NO'
-        if target.name.include?('CFStream')
-          config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_CFSTREAM=1'
-        else
-          config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_CRONET_WITH_PACKET_COALESCING=1'
-        end
+        config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_CRONET_WITH_PACKET_COALESCING=1 GRPC_CFSTREAM=1'
       end
     end
 
@@ -139,7 +135,7 @@
     # the test target 'InteropTestsRemoteWithCronet'
     # Activate GRPCCall+InternalTests functions for the dedicated build configuration 'Test', which will
     # be used by all test targets using it.
-    if target.name == 'gRPC' || target.name.start_with?('gRPC.')
+    if /gRPC-(mac|i)OS/.match(target.name)
       target.build_configurations.each do |config|
         if config.name == 'Cronet'
           config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_COMPILE_WITH_CRONET=1 GRPC_TEST_OBJC=1'
@@ -150,9 +146,7 @@
     end
 
     # Enable NSAssert on gRPC
-    if target.name == 'gRPC' || target.name.start_with?('gRPC.') ||
-        target.name == 'ProtoRPC' || target.name.start_with?('ProtoRPC.') ||
-        target.name == 'RxLibrary' || target.name.start_with?('RxLibrary.') 
+    if /(gRPC|ProtoRPC|RxLibrary)-(mac|i)OS/.match(target.name)
       target.build_configurations.each do |config|
         if config.name != 'Release'
           config.build_settings['ENABLE_NS_ASSERTIONS'] = 'YES'
diff --git a/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec b/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec
index ad83481..93b56d5 100644
--- a/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec
+++ b/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec
@@ -14,20 +14,34 @@
   s.dependency "!ProtoCompiler-gRPCPlugin"
 
   repo_root = '../../../..'
-  bin_dir = "#{repo_root}/bins/$CONFIG"
+  config = ENV['CONFIG'] || 'opt'
+  bin_dir = "#{repo_root}/bins/#{config}"
 
   protoc = "#{bin_dir}/protobuf/protoc"
   well_known_types_dir = "#{repo_root}/third_party/protobuf/src"
   plugin = "#{bin_dir}/grpc_objective_c_plugin"
 
   s.prepare_command = <<-CMD
-    #{protoc} \
-        --plugin=protoc-gen-grpc=#{plugin} \
-        --objc_out=. \
-        --grpc_out=. \
-        -I . \
-        -I #{well_known_types_dir} \
-        *.proto
+    if [ -f #{protoc} ]; then
+      #{protoc} \
+          --plugin=protoc-gen-grpc=#{plugin} \
+          --objc_out=. \
+          --grpc_out=. \
+          -I . \
+          -I #{well_known_types_dir} \
+          *.proto
+    else
+      # protoc was not found bin_dir, use installed version instead
+      (>&2 echo "\nWARNING: Using installed version of protoc. It might be incompatible with gRPC")
+
+      protoc \
+          --plugin=protoc-gen-grpc=#{plugin} \
+          --objc_out=. \
+          --grpc_out=. \
+          -I . \
+          -I #{well_known_types_dir} \
+          *.proto
+    fi
   CMD
 
   s.subspec "Messages" do |ms|
diff --git a/src/objective-c/tests/RxLibraryUnitTests.m b/src/objective-c/tests/RxLibraryUnitTests.m
index ecd914c..2faf57e 100644
--- a/src/objective-c/tests/RxLibraryUnitTests.m
+++ b/src/objective-c/tests/RxLibraryUnitTests.m
@@ -16,7 +16,6 @@
  *
  */
 
-#import <UIKit/UIKit.h>
 #import <XCTest/XCTest.h>
 
 #import <RxLibrary/GRXBufferedPipe.h>
@@ -44,9 +43,9 @@
 
 - (GRXSingleHandler)block {
   return ^(id value, NSError *errorOrNil) {
-    ++_timesCalled;
-    _value = value;
-    _errorOrNil = errorOrNil;
+    ++self->_timesCalled;
+    self->_value = value;
+    self->_errorOrNil = errorOrNil;
   };
 }
 @end
diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
index b6762cc..27a617c 100644
--- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
+++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
@@ -66,7 +66,20 @@
 		6C1A3F81CCF7C998B4813EFD /* libPods-InteropTestsCallOptions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF3FC2CFFE7B0961823BC740 /* libPods-InteropTestsCallOptions.a */; };
 		886717A79EFF774F356798E6 /* libPods-InteropTestsMultipleChannels.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 355D0E30AD224763BC9519F4 /* libPods-InteropTestsMultipleChannels.a */; };
 		91D4B3C85B6D8562F409CB48 /* libPods-InteropTestsLocalSSLCFStream.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3AB031E0E26AC8EF30A2A2A /* libPods-InteropTestsLocalSSLCFStream.a */; };
+		953CD2942A3A6D6CE695BE87 /* libPods-MacTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 276873A05AC5479B60DF6079 /* libPods-MacTests.a */; };
 		98478C9F42329DF769A45B6C /* libPods-APIv2Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B6AD69CACF67505B0F028E92 /* libPods-APIv2Tests.a */; };
+		B071230B22669EED004B64A1 /* StressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B071230A22669EED004B64A1 /* StressTests.m */; };
+		B0BB3F02225E7A3C008DA580 /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; };
+		B0BB3F03225E7A44008DA580 /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; };
+		B0BB3F04225E7A8D008DA580 /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; };
+		B0BB3F05225E7A9F008DA580 /* InteropTestsRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 6379CC4F1BE16703001BC0A1 /* InteropTestsRemote.m */; };
+		B0BB3F06225E7AAD008DA580 /* GRPCClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */; };
+		B0BB3F07225E7AB5008DA580 /* APIv2Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B95A421CAC6C500C0A151 /* APIv2Tests.m */; };
+		B0BB3F08225E7ABA008DA580 /* UnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E0282E8215AA697007AC99D /* UnitTests.m */; };
+		B0BB3F0A225EA511008DA580 /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; };
+		B0BB3F0B225EB110008DA580 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; };
+		B0D39B9A2266F3CB00A4078D /* StressTestsSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = B0D39B992266F3CB00A4078D /* StressTestsSSL.m */; };
+		B0D39B9C2266FF9800A4078D /* StressTestsCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = B0D39B9B2266FF9800A4078D /* StressTestsCleartext.m */; };
 		BC111C80CBF7068B62869352 /* libPods-InteropTestsRemoteCFStream.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F44AC3F44E3491A8C0D890FE /* libPods-InteropTestsRemoteCFStream.a */; };
 		C3D6F4270A2FFF634D8849ED /* libPods-InteropTestsLocalCleartextCFStream.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BDA4BA011779D5D25B5618C /* libPods-InteropTestsLocalCleartextCFStream.a */; };
 		CCF5C0719EF608276AE16374 /* libPods-UnitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 22A3EBB488699C8CEA19707B /* libPods-UnitTests.a */; };
@@ -183,10 +196,14 @@
 		12B238CD1702393C2BA5DE80 /* Pods-APIv2Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-APIv2Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests.release.xcconfig"; sourceTree = "<group>"; };
 		14B09A58FEE53A7A6B838920 /* Pods-InteropTestsLocalSSL.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.cronet.xcconfig"; sourceTree = "<group>"; };
 		1588C85DEAF7FC0ACDEA4C02 /* Pods-InteropTestsLocalCleartext.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.test.xcconfig"; sourceTree = "<group>"; };
+		16A2E4C5839C83FBDA63881F /* Pods-MacTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MacTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-MacTests/Pods-MacTests.cronet.xcconfig"; sourceTree = "<group>"; };
 		17F60BF2871F6AF85FB3FA12 /* Pods-InteropTestsRemoteWithCronet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; sourceTree = "<group>"; };
+		1E43EAE443CBB4482B1EB071 /* Pods-MacTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MacTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MacTests/Pods-MacTests.release.xcconfig"; sourceTree = "<group>"; };
+		1F5E788FBF9A4A06EB9E1ACD /* Pods-MacTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MacTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-MacTests/Pods-MacTests.test.xcconfig"; sourceTree = "<group>"; };
 		20DFF2F3C97EF098FE5A3171 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		22A3EBB488699C8CEA19707B /* libPods-UnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-UnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		2650FEF00956E7924772F9D9 /* Pods-InteropTestsMultipleChannels.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsMultipleChannels.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels.release.xcconfig"; sourceTree = "<group>"; };
+		276873A05AC5479B60DF6079 /* libPods-MacTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MacTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		2B89F3037963E6EDDD48D8C3 /* Pods-InteropTestsRemoteWithCronet.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.test.xcconfig"; sourceTree = "<group>"; };
 		303F4A17EB1650FC44603D17 /* Pods-InteropTestsRemoteCFStream.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteCFStream.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteCFStream/Pods-InteropTestsRemoteCFStream.release.xcconfig"; sourceTree = "<group>"; };
 		32748C4078AEB05F8F954361 /* Pods-InteropTestsRemoteCFStream.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteCFStream.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteCFStream/Pods-InteropTestsRemoteCFStream.debug.xcconfig"; sourceTree = "<group>"; };
@@ -274,6 +291,12 @@
 		AA7CB64B4DD9915AE7C03163 /* Pods-InteropTestsLocalCleartext.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.cronet.xcconfig"; sourceTree = "<group>"; };
 		AC414EF7A6BF76ED02B6E480 /* Pods-InteropTestsRemoteWithCronet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.release.xcconfig"; sourceTree = "<group>"; };
 		AF3FC2CFFE7B0961823BC740 /* libPods-InteropTestsCallOptions.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsCallOptions.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		B071230A22669EED004B64A1 /* StressTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StressTests.m; sourceTree = "<group>"; };
+		B0BB3EF7225E795F008DA580 /* MacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MacTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		B0BB3EFB225E795F008DA580 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		B0C5FC172267C77200F192BE /* StressTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StressTests.h; sourceTree = "<group>"; };
+		B0D39B992266F3CB00A4078D /* StressTestsSSL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StressTestsSSL.m; sourceTree = "<group>"; };
+		B0D39B9B2266FF9800A4078D /* StressTestsCleartext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StressTestsCleartext.m; sourceTree = "<group>"; };
 		B226619DC4E709E0FFFF94B8 /* Pods-CronetUnitTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.test.xcconfig"; sourceTree = "<group>"; };
 		B6AD69CACF67505B0F028E92 /* libPods-APIv2Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-APIv2Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		B94C27C06733CF98CE1B2757 /* Pods-AllTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.debug.xcconfig"; sourceTree = "<group>"; };
@@ -290,6 +313,7 @@
 		DC3CA1D948F068E76957A861 /* Pods-InteropTestsRemote.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.debug.xcconfig"; sourceTree = "<group>"; };
 		E1486220285AF123EB124008 /* Pods-InteropTestsLocalCleartext.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.debug.xcconfig"; sourceTree = "<group>"; };
 		E1E7660656D902104F728892 /* Pods-UnitTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.cronet.xcconfig"; sourceTree = "<group>"; };
+		E3ACD4D5902745976D9C2229 /* Pods-MacTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MacTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MacTests/Pods-MacTests.debug.xcconfig"; sourceTree = "<group>"; };
 		E4275A759BDBDF143B9B438F /* Pods-InteropTestsRemote.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.release.xcconfig"; sourceTree = "<group>"; };
 		E4FD4606D4AB8D5A314D72F0 /* Pods-InteropTestsLocalCleartextCFStream.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartextCFStream.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartextCFStream/Pods-InteropTestsLocalCleartextCFStream.test.xcconfig"; sourceTree = "<group>"; };
 		E7E4D3FD76E3B745D992AF5F /* Pods-AllTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.cronet.xcconfig"; sourceTree = "<group>"; };
@@ -451,6 +475,14 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		B0BB3EF4225E795F008DA580 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				953CD2942A3A6D6CE695BE87 /* libPods-MacTests.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
@@ -475,6 +507,7 @@
 				AF3FC2CFFE7B0961823BC740 /* libPods-InteropTestsCallOptions.a */,
 				22A3EBB488699C8CEA19707B /* libPods-UnitTests.a */,
 				B6AD69CACF67505B0F028E92 /* libPods-APIv2Tests.a */,
+				276873A05AC5479B60DF6079 /* libPods-MacTests.a */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -548,6 +581,10 @@
 				51F2A64B7AADBA1B225B132E /* Pods-APIv2Tests.test.xcconfig */,
 				8C233E85C3EB45B3CAE52EDF /* Pods-APIv2Tests.cronet.xcconfig */,
 				12B238CD1702393C2BA5DE80 /* Pods-APIv2Tests.release.xcconfig */,
+				E3ACD4D5902745976D9C2229 /* Pods-MacTests.debug.xcconfig */,
+				1F5E788FBF9A4A06EB9E1ACD /* Pods-MacTests.test.xcconfig */,
+				16A2E4C5839C83FBDA63881F /* Pods-MacTests.cronet.xcconfig */,
+				1E43EAE443CBB4482B1EB071 /* Pods-MacTests.release.xcconfig */,
 			);
 			name = Pods;
 			sourceTree = "<group>";
@@ -637,6 +674,7 @@
 				5E7D71B3210B9EC9001EA6BA /* InteropTestsCallOptions */,
 				5E0282E7215AA697007AC99D /* UnitTests */,
 				5E3B95A321CAC6C500C0A151 /* APIv2Tests */,
+				B0BB3EF8225E795F008DA580 /* MacTests */,
 				635697C81B14FC11007A7283 /* Products */,
 				51E4650F34F854F41FF053B3 /* Pods */,
 				136D535E19727099B941D7B1 /* Frameworks */,
@@ -663,6 +701,7 @@
 				5E7D71B2210B9EC8001EA6BA /* InteropTestsCallOptions.xctest */,
 				5E0282E6215AA697007AC99D /* UnitTests.xctest */,
 				5E3B95A221CAC6C500C0A151 /* APIv2Tests.xctest */,
+				B0BB3EF7225E795F008DA580 /* MacTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -692,6 +731,18 @@
 			name = "Supporting Files";
 			sourceTree = "<group>";
 		};
+		B0BB3EF8225E795F008DA580 /* MacTests */ = {
+			isa = PBXGroup;
+			children = (
+				B0BB3EFB225E795F008DA580 /* Info.plist */,
+				B071230A22669EED004B64A1 /* StressTests.m */,
+				B0D39B992266F3CB00A4078D /* StressTestsSSL.m */,
+				B0D39B9B2266FF9800A4078D /* StressTestsCleartext.m */,
+				B0C5FC172267C77200F192BE /* StressTests.h */,
+			);
+			path = MacTests;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -1030,6 +1081,25 @@
 			productReference = 63DC84431BE152B5000708E8 /* InteropTestsLocalCleartext.xctest */;
 			productType = "com.apple.product-type.bundle.unit-test";
 		};
+		B0BB3EF6225E795F008DA580 /* MacTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = B0BB3EFC225E795F008DA580 /* Build configuration list for PBXNativeTarget "MacTests" */;
+			buildPhases = (
+				E5B20F69559C6AE299DFEA7C /* [CP] Check Pods Manifest.lock */,
+				B0BB3EF3225E795F008DA580 /* Sources */,
+				B0BB3EF4225E795F008DA580 /* Frameworks */,
+				B0BB3EF5225E795F008DA580 /* Resources */,
+				452FDC3918FEC23ECAFD31EC /* [CP] Copy Pods Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = MacTests;
+			productName = MacTests;
+			productReference = B0BB3EF7225E795F008DA580 /* MacTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
@@ -1098,6 +1168,10 @@
 					63DC84421BE152B5000708E8 = {
 						CreatedOnToolsVersion = 7.0.1;
 					};
+					B0BB3EF6225E795F008DA580 = {
+						CreatedOnToolsVersion = 10.1;
+						ProvisioningStyle = Automatic;
+					};
 				};
 			};
 			buildConfigurationList = 635697C21B14FC11007A7283 /* Build configuration list for PBXProject "Tests" */;
@@ -1129,6 +1203,7 @@
 				5E7D71B1210B9EC8001EA6BA /* InteropTestsCallOptions */,
 				5E0282E5215AA697007AC99D /* UnitTests */,
 				5E3B95A121CAC6C500C0A151 /* APIv2Tests */,
+				B0BB3EF6225E795F008DA580 /* MacTests */,
 			);
 		};
 /* End PBXProject section */
@@ -1250,6 +1325,14 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		B0BB3EF5225E795F008DA580 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B0BB3F0A225EA511008DA580 /* TestCertificates.bundle in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
@@ -1278,7 +1361,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsMultipleChannels/Pods-InteropTestsMultipleChannels-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1296,7 +1379,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-ChannelTests/Pods-ChannelTests-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1361,6 +1444,28 @@
 			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
+		452FDC3918FEC23ECAFD31EC /* [CP] Copy Pods Resources */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${SRCROOT}/Pods/Target Support Files/Pods-MacTests/Pods-MacTests-resources.sh",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-macOS/gRPCCertificates.bundle",
+			);
+			name = "[CP] Copy Pods Resources";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MacTests/Pods-MacTests-resources.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
 		483CDBBAEAEFCB530ADDDDD5 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
@@ -1404,7 +1509,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsLocalSSLCFStream/Pods-InteropTestsLocalSSLCFStream-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC.default-CFStream/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1440,7 +1545,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1458,7 +1563,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsCallOptions/Pods-InteropTestsCallOptions-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1512,7 +1617,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1584,7 +1689,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsRemoteCFStream/Pods-InteropTestsRemoteCFStream-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC.default-CFStream/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1602,7 +1707,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1620,7 +1725,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsLocalCleartextCFStream/Pods-InteropTestsLocalCleartextCFStream-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC.default-CFStream/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1638,7 +1743,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-AllTests/Pods-AllTests-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1712,7 +1817,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-APIv2Tests/Pods-APIv2Tests-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputFileListPaths = (
@@ -1732,7 +1837,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1750,7 +1855,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1768,7 +1873,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -1779,6 +1884,28 @@
 			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet-resources.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
+		E5B20F69559C6AE299DFEA7C /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-MacTests-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
 		E63468C760D0724F18861822 /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
@@ -2027,6 +2154,24 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		B0BB3EF3225E795F008DA580 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B0BB3F07225E7AB5008DA580 /* APIv2Tests.m in Sources */,
+				B0BB3F08225E7ABA008DA580 /* UnitTests.m in Sources */,
+				B071230B22669EED004B64A1 /* StressTests.m in Sources */,
+				B0BB3F05225E7A9F008DA580 /* InteropTestsRemote.m in Sources */,
+				B0D39B9A2266F3CB00A4078D /* StressTestsSSL.m in Sources */,
+				B0BB3F03225E7A44008DA580 /* InteropTestsLocalCleartext.m in Sources */,
+				B0BB3F04225E7A8D008DA580 /* RxLibraryUnitTests.m in Sources */,
+				B0D39B9C2266FF9800A4078D /* StressTestsCleartext.m in Sources */,
+				B0BB3F0B225EB110008DA580 /* InteropTests.m in Sources */,
+				B0BB3F02225E7A3C008DA580 /* InteropTestsLocalSSL.m in Sources */,
+				B0BB3F06225E7AAD008DA580 /* GRPCClientTests.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
@@ -3886,6 +4031,154 @@
 			};
 			name = Release;
 		};
+		B0BB3EFD225E795F008DA580 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = E3ACD4D5902745976D9C2229 /* Pods-MacTests.debug.xcconfig */;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_IDENTITY = "-";
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				INFOPLIST_FILE = MacTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = io.grpc.MacTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		B0BB3EFE225E795F008DA580 /* Test */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 1F5E788FBF9A4A06EB9E1ACD /* Pods-MacTests.test.xcconfig */;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_IDENTITY = "-";
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"$(inherited)",
+					"COCOAPODS=1",
+					"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
+					"PB_FIELD_32BIT=1",
+					"PB_NO_PACKED_STRUCTS=1",
+					"PB_ENABLE_MALLOC=1",
+					"GRPC_TEST_OBJC=1",
+				);
+				INFOPLIST_FILE = MacTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = io.grpc.MacTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+			};
+			name = Test;
+		};
+		B0BB3EFF225E795F008DA580 /* Cronet */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 16A2E4C5839C83FBDA63881F /* Pods-MacTests.cronet.xcconfig */;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_IDENTITY = "-";
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				INFOPLIST_FILE = MacTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = io.grpc.MacTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+			};
+			name = Cronet;
+		};
+		B0BB3F00225E795F008DA580 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 1E43EAE443CBB4482B1EB071 /* Pods-MacTests.release.xcconfig */;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_IDENTITY = "-";
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				INFOPLIST_FILE = MacTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = io.grpc.MacTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -4087,6 +4380,17 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		B0BB3EFC225E795F008DA580 /* Build configuration list for PBXNativeTarget "MacTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				B0BB3EFD225E795F008DA580 /* Debug */,
+				B0BB3EFE225E795F008DA580 /* Test */,
+				B0BB3EFF225E795F008DA580 /* Cronet */,
+				B0BB3F00225E795F008DA580 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 	};
 	rootObject = 635697BF1B14FC11007A7283 /* Project object */;
diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
index b0bb933..a2560fe 100644
--- a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
+++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
@@ -26,7 +26,6 @@
       buildConfiguration = "Test"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      language = ""
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
          <TestableReference
@@ -67,7 +66,6 @@
       buildConfiguration = "Test"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      language = ""
       launchStyle = "0"
       useCustomWorkingDirectory = "NO"
       ignoresPersistentStateOnLaunch = "NO"
diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/MacTests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/MacTests.xcscheme
new file mode 100644
index 0000000..f84ac7f
--- /dev/null
+++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/MacTests.xcscheme
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1010"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "NO"
+            buildForArchiving = "NO"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "B0BB3EF6225E795F008DA580"
+               BuildableName = "MacTests.xctest"
+               BlueprintName = "MacTests"
+               ReferencedContainer = "container:Tests.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Test"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "B0BB3EF6225E795F008DA580"
+               BuildableName = "MacTests.xctest"
+               BlueprintName = "MacTests"
+               ReferencedContainer = "container:Tests.xcodeproj">
+            </BuildableReference>
+            <SkippedTests>
+               <Test
+                  Identifier = "InteropTests">
+               </Test>
+               <Test
+                  Identifier = "StressTests">
+               </Test>
+            </SkippedTests>
+         </TestableReference>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "B0BB3EF6225E795F008DA580"
+            BuildableName = "MacTests.xctest"
+            BlueprintName = "MacTests"
+            ReferencedContainer = "container:Tests.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "B0BB3EF6225E795F008DA580"
+            BuildableName = "MacTests.xctest"
+            BlueprintName = "MacTests"
+            ReferencedContainer = "container:Tests.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme
new file mode 100644
index 0000000..77f567d
--- /dev/null
+++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1010"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "635697C61B14FC11007A7283"
+               BuildableName = "libTests.a"
+               BlueprintName = "Tests"
+               ReferencedContainer = "container:Tests.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "635697C61B14FC11007A7283"
+            BuildableName = "libTests.a"
+            BlueprintName = "Tests"
+            ReferencedContainer = "container:Tests.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "635697C61B14FC11007A7283"
+            BuildableName = "libTests.a"
+            BlueprintName = "Tests"
+            ReferencedContainer = "container:Tests.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/src/objective-c/tests/UnitTests/UnitTests.m b/src/objective-c/tests/UnitTests/UnitTests.m
index 57e686d..4dcb8c0 100644
--- a/src/objective-c/tests/UnitTests/UnitTests.m
+++ b/src/objective-c/tests/UnitTests/UnitTests.m
@@ -20,7 +20,7 @@
 
 #import <GRPCClient/GRPCCall.h>
 
-#import "src/objective-c/GRPCClient/private/NSError+GRPC.h"
+#import "../../GRPCClient/private/NSError+GRPC.h"
 
 @interface UnitTests : XCTestCase
 
diff --git a/src/objective-c/tests/run_tests.sh b/src/objective-c/tests/run_tests.sh
index f6fea96..8c768cb 100755
--- a/src/objective-c/tests/run_tests.sh
+++ b/src/objective-c/tests/run_tests.sh
@@ -195,4 +195,17 @@
     | egrep -v '^$' \
     | egrep -v "(GPBDictionary|GPBArray)" -
 
+echo "TIME:  $(date)"
+xcodebuild \
+    -workspace Tests.xcworkspace \
+    -scheme MacTests \
+    -destination platform=macOS \
+    HOST_PORT_LOCALSSL=localhost:5051 \
+    HOST_PORT_LOCAL=localhost:5050 \
+    HOST_PORT_REMOTE=grpc-test.sandbox.googleapis.com \
+    test \
+    | egrep -v "$XCODEBUILD_FILTER" \
+    | egrep -v '^$' \
+    | egrep -v "(GPBDictionary|GPBArray)" -
+
 exit 0
diff --git a/src/objective-c/tests/version.h b/src/objective-c/tests/version.h
index 39d38a9..87516de 100644
--- a/src/objective-c/tests/version.h
+++ b/src/objective-c/tests/version.h
@@ -22,5 +22,5 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.20.1"
+#define GRPC_OBJC_VERSION_STRING @"1.21.0-dev"
 #define GRPC_C_VERSION_STRING @"7.0.0"
diff --git a/src/php/composer.json b/src/php/composer.json
index 3ae0ce5..a9d0aeb 100644
--- a/src/php/composer.json
+++ b/src/php/composer.json
@@ -2,7 +2,7 @@
   "name": "grpc/grpc-dev",
   "description": "gRPC library for PHP - for Developement use only",
   "license": "Apache-2.0",
-  "version": "1.20.1",
+  "version": "1.21.0",
   "require": {
     "php": ">=5.5.0",
     "google/protobuf": "^v3.3.0"
diff --git a/src/php/ext/grpc/version.h b/src/php/ext/grpc/version.h
index 3aaf1d8..43b3fa6 100644
--- a/src/php/ext/grpc/version.h
+++ b/src/php/ext/grpc/version.h
@@ -20,6 +20,6 @@
 #ifndef VERSION_H
 #define VERSION_H
 
-#define PHP_GRPC_VERSION "1.20.1"
+#define PHP_GRPC_VERSION "1.21.0dev"
 
 #endif /* VERSION_H */
diff --git a/src/proto/grpc/channelz/BUILD b/src/proto/grpc/channelz/BUILD
index b6b485e..1d80ec2 100644
--- a/src/proto/grpc/channelz/BUILD
+++ b/src/proto/grpc/channelz/BUILD
@@ -25,6 +25,11 @@
     well_known_protos = True,
 )
 
+proto_library(
+    name = "channelz_proto_descriptors",
+    srcs = ["channelz.proto"],
+)
+
 filegroup(
     name = "channelz_proto_file",
     srcs = [
diff --git a/src/proto/grpc/health/v1/BUILD b/src/proto/grpc/health/v1/BUILD
index 38a7d99..fc58e8a 100644
--- a/src/proto/grpc/health/v1/BUILD
+++ b/src/proto/grpc/health/v1/BUILD
@@ -23,6 +23,11 @@
     srcs = ["health.proto"],
 )
 
+proto_library(
+    name = "health_proto_descriptor",
+    srcs = ["health.proto"],
+)
+
 filegroup(
     name = "health_proto_file",
     srcs = [
diff --git a/src/proto/grpc/reflection/v1alpha/BUILD b/src/proto/grpc/reflection/v1alpha/BUILD
index 4d919d0..5424c0d 100644
--- a/src/proto/grpc/reflection/v1alpha/BUILD
+++ b/src/proto/grpc/reflection/v1alpha/BUILD
@@ -23,6 +23,11 @@
     srcs = ["reflection.proto"],
 )
 
+proto_library(
+    name = "reflection_proto_descriptor",
+    srcs = ["reflection.proto"],
+)
+
 filegroup(
     name = "reflection_proto_file",
     srcs = [
diff --git a/src/proto/grpc/testing/BUILD b/src/proto/grpc/testing/BUILD
index 9876d51..727c99c 100644
--- a/src/proto/grpc/testing/BUILD
+++ b/src/proto/grpc/testing/BUILD
@@ -16,7 +16,7 @@
 
 load("//bazel:grpc_build_system.bzl", "grpc_proto_library", "grpc_package")
 load("@grpc_python_dependencies//:requirements.bzl", "requirement")
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
+load("//bazel:python_rules.bzl", "py_proto_library")
 
 grpc_package(name = "testing", visibility = "public")
 
@@ -61,13 +61,14 @@
     has_services = False,
 )
 
+proto_library(
+    name = "empty_proto_descriptor",
+    srcs = ["empty.proto"],
+)
+
 py_proto_library(
     name = "py_empty_proto",
-    protos = ["empty.proto",],
-    with_grpc = True,
-    deps = [
-        requirement('protobuf'),
-    ],
+    deps = [":empty_proto_descriptor"],
 )
 
 grpc_proto_library(
@@ -76,13 +77,14 @@
     has_services = False,
 )
 
+proto_library(
+    name = "messages_proto_descriptor",
+    srcs = ["messages.proto"],
+)
+
 py_proto_library(
     name = "py_messages_proto",
-    protos = ["messages.proto",],
-    with_grpc = True,
-    deps = [
-        requirement('protobuf'),
-    ],
+    deps = [":messages_proto_descriptor"],
 )
 
 grpc_proto_library(
@@ -144,16 +146,50 @@
     ],
 )
 
+# Test that grpc_proto_library/cc_grpc_library can consume generated files
+genrule(
+    name = "messages_gen_proto_file",
+    srcs = ["messages.proto"],
+    outs = ["messages_gen.proto"],
+    cmd = "cp $< $@",
+)
+
+grpc_proto_library(
+    name = "messages_gen_proto",
+    srcs = ["messages_gen_proto_file"],
+    has_services = False,
+)
+
+genrule(
+    name = "test_gen_proto_file",
+    srcs = ["test.proto"],
+    outs = ["test_gen.proto"],
+    cmd = "sed 's/messages.proto/messages_gen.proto/' $< > $@",
+)
+
+# Consume generated files in srcs and in deps
+grpc_proto_library(
+    name = "test_gen_proto",
+    srcs = ["test_gen_proto_file"],
+    deps = [
+        "empty_proto",
+        "messages_gen_proto",
+    ],
+)
+
+proto_library(
+    name = "test_proto_descriptor",
+    srcs = ["test.proto"],
+    deps = [
+        ":empty_proto_descriptor",
+        ":messages_proto_descriptor",
+    ],
+)
+
 py_proto_library(
     name = "py_test_proto",
-    protos = ["test.proto",],
-    with_grpc = True,
     deps = [
-        requirement('protobuf'),
-    ],
-    proto_deps = [
-        ":py_empty_proto",
-        ":py_messages_proto",
+        ":test_proto_descriptor",
     ]
 )
 
diff --git a/src/proto/grpc/testing/echo_messages.proto b/src/proto/grpc/testing/echo_messages.proto
index 2f93530..2c773ea 100644
--- a/src/proto/grpc/testing/echo_messages.proto
+++ b/src/proto/grpc/testing/echo_messages.proto
@@ -17,6 +17,8 @@
 
 package grpc.testing;
 
+option cc_enable_arenas = true;
+
 // Message to be echoed back serialized in trailer.
 message DebugInfo {
   repeated string stack_entries = 1;
@@ -47,6 +49,7 @@
   ErrorStatus expected_error = 14;
   int32 server_sleep_us = 15; // Amount to sleep when invoking server
   int32 backend_channel_idx = 16; // which backend to send request to
+  bool echo_metadata_initially = 17;
 }
 
 message EchoRequest {
diff --git a/src/proto/grpc/testing/proto2/BUILD.bazel b/src/proto/grpc/testing/proto2/BUILD.bazel
index c4c4f00..e939c52 100644
--- a/src/proto/grpc/testing/proto2/BUILD.bazel
+++ b/src/proto/grpc/testing/proto2/BUILD.bazel
@@ -1,30 +1,32 @@
 load("@grpc_python_dependencies//:requirements.bzl", "requirement")
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
 
 package(default_visibility = ["//visibility:public"])
+load("//bazel:python_rules.bzl", "py_proto_library")
+
+proto_library(
+    name = "empty2_proto_descriptor",
+    srcs = ["empty2.proto"],
+)
 
 py_proto_library(
     name = "empty2_proto",
-    protos = [
-        "empty2.proto",
-    ],
-    with_grpc = True,
     deps = [
-        requirement('protobuf'),
+        ":empty2_proto_descriptor",
     ],
 )
 
+proto_library(
+    name = "empty2_extensions_proto_descriptor",
+    srcs = ["empty2_extensions.proto"],
+    deps = [
+        ":empty2_proto_descriptor",
+    ]
+)
+
 py_proto_library(
     name = "empty2_extensions_proto",
-    protos = [
-        "empty2_extensions.proto",
-    ],
-    proto_deps = [
-        ":empty2_proto",
-    ],
-    with_grpc = True,
     deps = [
-        requirement('protobuf'),
+        ":empty2_extensions_proto_descriptor",
     ],
 )
 
diff --git a/src/python/grpcio/grpc/BUILD.bazel b/src/python/grpcio/grpc/BUILD.bazel
index 27d5d2e..a2bedae 100644
--- a/src/python/grpcio/grpc/BUILD.bazel
+++ b/src/python/grpcio/grpc/BUILD.bazel
@@ -12,6 +12,7 @@
         ":channel",
         ":interceptor",
         ":server",
+        ":compression",
         "//src/python/grpcio/grpc/_cython:cygrpc",
         "//src/python/grpcio/grpc/experimental",
         "//src/python/grpcio/grpc/framework",
@@ -32,11 +33,17 @@
 )
 
 py_library(
+    name = "compression",
+    srcs = ["_compression.py"],
+)
+
+py_library(
     name = "channel",
     srcs = ["_channel.py"],
     deps = [
         ":common",
         ":grpcio_metadata",
+        ":compression",
     ],
 )
 
@@ -68,6 +75,7 @@
     srcs = ["_server.py"],
     deps = [
         ":common",
+        ":compression",
         ":interceptor",
     ],
 )
diff --git a/src/python/grpcio/grpc/__init__.py b/src/python/grpcio/grpc/__init__.py
index 7631410..6175180 100644
--- a/src/python/grpcio/grpc/__init__.py
+++ b/src/python/grpcio/grpc/__init__.py
@@ -21,6 +21,7 @@
 import six
 
 from grpc._cython import cygrpc as _cygrpc
+from grpc import _compression
 
 logging.getLogger(__name__).addHandler(logging.NullHandler())
 
@@ -413,6 +414,8 @@
       credentials: An optional CallCredentials for the RPC.
       wait_for_ready: This is an EXPERIMENTAL argument. An optional flag t
         enable wait for ready mechanism.
+      compression: An element of grpc.compression, e.g.
+        grpc.compression.Gzip. This is an EXPERIMENTAL option.
     """
 
 
@@ -669,7 +672,8 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         """Synchronously invokes the underlying RPC.
 
         Args:
@@ -681,6 +685,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
           The response value for the RPC.
@@ -698,7 +704,8 @@
                   timeout=None,
                   metadata=None,
                   credentials=None,
-                  wait_for_ready=None):
+                  wait_for_ready=None,
+                  compression=None):
         """Synchronously invokes the underlying RPC.
 
         Args:
@@ -710,6 +717,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
           The response value for the RPC and a Call value for the RPC.
@@ -727,7 +736,8 @@
                timeout=None,
                metadata=None,
                credentials=None,
-               wait_for_ready=None):
+               wait_for_ready=None,
+               compression=None):
         """Asynchronously invokes the underlying RPC.
 
         Args:
@@ -739,6 +749,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
             An object that is both a Call for the RPC and a Future.
@@ -759,7 +771,8 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         """Invokes the underlying RPC.
 
         Args:
@@ -771,6 +784,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
             An object that is both a Call for the RPC and an iterator of
@@ -790,7 +805,8 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         """Synchronously invokes the underlying RPC.
 
         Args:
@@ -803,6 +819,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
           The response value for the RPC.
@@ -820,7 +838,8 @@
                   timeout=None,
                   metadata=None,
                   credentials=None,
-                  wait_for_ready=None):
+                  wait_for_ready=None,
+                  compression=None):
         """Synchronously invokes the underlying RPC on the client.
 
         Args:
@@ -833,6 +852,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
           The response value for the RPC and a Call object for the RPC.
@@ -850,7 +871,8 @@
                timeout=None,
                metadata=None,
                credentials=None,
-               wait_for_ready=None):
+               wait_for_ready=None,
+               compression=None):
         """Asynchronously invokes the underlying RPC on the client.
 
         Args:
@@ -862,6 +884,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
             An object that is both a Call for the RPC and a Future.
@@ -882,7 +906,8 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         """Invokes the underlying RPC on the client.
 
         Args:
@@ -894,6 +919,8 @@
           credentials: An optional CallCredentials for the RPC.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip. This is an EXPERIMENTAL option.
 
         Returns:
             An object that is both a Call for the RPC and an iterator of
@@ -1097,6 +1124,17 @@
         """
         raise NotImplementedError()
 
+    def set_compression(self, compression):
+        """Set the compression algorithm to be used for the entire call.
+
+        This is an EXPERIMENTAL method.
+
+        Args:
+          compression: An element of grpc.compression, e.g.
+            grpc.compression.Gzip.
+        """
+        raise NotImplementedError()
+
     @abc.abstractmethod
     def send_initial_metadata(self, initial_metadata):
         """Sends the initial metadata value to the client.
@@ -1184,6 +1222,16 @@
         """
         raise NotImplementedError()
 
+    def disable_next_message_compression(self):
+        """Disables compression for the next response message.
+
+        This is an EXPERIMENTAL method.
+
+        This method will override any compression configuration set during
+        server creation or set on the call.
+        """
+        raise NotImplementedError()
+
 
 #####################  Service-Side Handler Interfaces  ########################
 
@@ -1682,7 +1730,7 @@
     return _utilities.channel_ready_future(channel)
 
 
-def insecure_channel(target, options=None):
+def insecure_channel(target, options=None, compression=None):
     """Creates an insecure Channel to a server.
 
     The returned Channel is thread-safe.
@@ -1691,15 +1739,18 @@
       target: The server address
       options: An optional list of key-value pairs (channel args
         in gRPC Core runtime) to configure the channel.
+      compression: An optional value indicating the compression method to be
+        used over the lifetime of the channel. This is an EXPERIMENTAL option.
 
     Returns:
       A Channel.
     """
     from grpc import _channel  # pylint: disable=cyclic-import
-    return _channel.Channel(target, () if options is None else options, None)
+    return _channel.Channel(target, ()
+                            if options is None else options, None, compression)
 
 
-def secure_channel(target, credentials, options=None):
+def secure_channel(target, credentials, options=None, compression=None):
     """Creates a secure Channel to a server.
 
     The returned Channel is thread-safe.
@@ -1709,13 +1760,15 @@
       credentials: A ChannelCredentials instance.
       options: An optional list of key-value pairs (channel args
         in gRPC Core runtime) to configure the channel.
+      compression: An optional value indicating the compression method to be
+        used over the lifetime of the channel. This is an EXPERIMENTAL option.
 
     Returns:
       A Channel.
     """
     from grpc import _channel  # pylint: disable=cyclic-import
     return _channel.Channel(target, () if options is None else options,
-                            credentials._credentials)
+                            credentials._credentials, compression)
 
 
 def intercept_channel(channel, *interceptors):
@@ -1750,7 +1803,8 @@
            handlers=None,
            interceptors=None,
            options=None,
-           maximum_concurrent_rpcs=None):
+           maximum_concurrent_rpcs=None,
+           compression=None):
     """Creates a Server with which RPCs can be serviced.
 
     Args:
@@ -1768,6 +1822,9 @@
       maximum_concurrent_rpcs: The maximum number of concurrent RPCs this server
         will service before returning RESOURCE_EXHAUSTED status, or None to
         indicate no limit.
+      compression: An element of grpc.compression, e.g.
+        grpc.compression.Gzip. This compression algorithm will be used for the
+        lifetime of the server unless overridden. This is an EXPERIMENTAL option.
 
     Returns:
       A Server object.
@@ -1777,7 +1834,7 @@
                                  if handlers is None else handlers, ()
                                  if interceptors is None else interceptors, ()
                                  if options is None else options,
-                                 maximum_concurrent_rpcs)
+                                 maximum_concurrent_rpcs, compression)
 
 
 @contextlib.contextmanager
@@ -1788,6 +1845,16 @@
     context._finalize_state()  # pylint: disable=protected-access
 
 
+class Compression(enum.IntEnum):
+    """Indicates the compression method to be used for an RPC.
+
+       This enumeration is part of an EXPERIMENTAL API.
+    """
+    NoCompression = _compression.NoCompression
+    Deflate = _compression.Deflate
+    Gzip = _compression.Gzip
+
+
 ###################################  __all__  #################################
 
 __all__ = (
@@ -1805,6 +1872,7 @@
     'AuthMetadataContext',
     'AuthMetadataPluginCallback',
     'AuthMetadataPlugin',
+    'Compression',
     'ClientCallDetails',
     'ServerCertificateConfiguration',
     'ServerCredentials',
diff --git a/src/python/grpcio/grpc/_channel.py b/src/python/grpcio/grpc/_channel.py
index ed4c871..1272ee8 100644
--- a/src/python/grpcio/grpc/_channel.py
+++ b/src/python/grpcio/grpc/_channel.py
@@ -19,6 +19,7 @@
 import time
 
 import grpc
+from grpc import _compression
 from grpc import _common
 from grpc import _grpcio_metadata
 from grpc._cython import cygrpc
@@ -512,17 +513,19 @@
         self._response_deserializer = response_deserializer
         self._context = cygrpc.build_census_context()
 
-    def _prepare(self, request, timeout, metadata, wait_for_ready):
+    def _prepare(self, request, timeout, metadata, wait_for_ready, compression):
         deadline, serialized_request, rendezvous = _start_unary_request(
             request, timeout, self._request_serializer)
         initial_metadata_flags = _InitialMetadataFlags().with_wait_for_ready(
             wait_for_ready)
+        augmented_metadata = _compression.augment_metadata(
+            metadata, compression)
         if serialized_request is None:
             return None, None, None, rendezvous
         else:
             state = _RPCState(_UNARY_UNARY_INITIAL_DUE, None, None, None, None)
             operations = (
-                cygrpc.SendInitialMetadataOperation(metadata,
+                cygrpc.SendInitialMetadataOperation(augmented_metadata,
                                                     initial_metadata_flags),
                 cygrpc.SendMessageOperation(serialized_request, _EMPTY_FLAGS),
                 cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
@@ -532,18 +535,17 @@
             )
             return state, operations, deadline, None
 
-    def _blocking(self, request, timeout, metadata, credentials,
-                  wait_for_ready):
+    def _blocking(self, request, timeout, metadata, credentials, wait_for_ready,
+                  compression):
         state, operations, deadline, rendezvous = self._prepare(
-            request, timeout, metadata, wait_for_ready)
+            request, timeout, metadata, wait_for_ready, compression)
         if state is None:
             raise rendezvous  # pylint: disable-msg=raising-bad-type
         else:
-            deadline_to_propagate = _determine_deadline(deadline)
             call = self._channel.segregated_call(
                 cygrpc.PropagationConstants.GRPC_PROPAGATE_DEFAULTS,
-                self._method, None, deadline_to_propagate, metadata, None
-                if credentials is None else credentials._credentials, ((
+                self._method, None, _determine_deadline(deadline), metadata,
+                None if credentials is None else credentials._credentials, ((
                     operations,
                     None,
                 ),), self._context)
@@ -556,9 +558,10 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         state, call, = self._blocking(request, timeout, metadata, credentials,
-                                      wait_for_ready)
+                                      wait_for_ready, compression)
         return _end_unary_response_blocking(state, call, False, None)
 
     def with_call(self,
@@ -566,9 +569,10 @@
                   timeout=None,
                   metadata=None,
                   credentials=None,
-                  wait_for_ready=None):
+                  wait_for_ready=None,
+                  compression=None):
         state, call, = self._blocking(request, timeout, metadata, credentials,
-                                      wait_for_ready)
+                                      wait_for_ready, compression)
         return _end_unary_response_blocking(state, call, True, None)
 
     def future(self,
@@ -576,9 +580,10 @@
                timeout=None,
                metadata=None,
                credentials=None,
-               wait_for_ready=None):
+               wait_for_ready=None,
+               compression=None):
         state, operations, deadline, rendezvous = self._prepare(
-            request, timeout, metadata, wait_for_ready)
+            request, timeout, metadata, wait_for_ready, compression)
         if state is None:
             raise rendezvous  # pylint: disable-msg=raising-bad-type
         else:
@@ -604,12 +609,14 @@
         self._response_deserializer = response_deserializer
         self._context = cygrpc.build_census_context()
 
-    def __call__(self,
-                 request,
-                 timeout=None,
-                 metadata=None,
-                 credentials=None,
-                 wait_for_ready=None):
+    def __call__(  # pylint: disable=too-many-locals
+            self,
+            request,
+            timeout=None,
+            metadata=None,
+            credentials=None,
+            wait_for_ready=None,
+            compression=None):
         deadline, serialized_request, rendezvous = _start_unary_request(
             request, timeout, self._request_serializer)
         initial_metadata_flags = _InitialMetadataFlags().with_wait_for_ready(
@@ -617,10 +624,12 @@
         if serialized_request is None:
             raise rendezvous  # pylint: disable-msg=raising-bad-type
         else:
+            augmented_metadata = _compression.augment_metadata(
+                metadata, compression)
             state = _RPCState(_UNARY_STREAM_INITIAL_DUE, None, None, None, None)
             operationses = (
                 (
-                    cygrpc.SendInitialMetadataOperation(metadata,
+                    cygrpc.SendInitialMetadataOperation(augmented_metadata,
                                                         initial_metadata_flags),
                     cygrpc.SendMessageOperation(serialized_request,
                                                 _EMPTY_FLAGS),
@@ -629,12 +638,13 @@
                 ),
                 (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
             )
-            event_handler = _event_handler(state, self._response_deserializer)
             call = self._managed_call(
                 cygrpc.PropagationConstants.GRPC_PROPAGATE_DEFAULTS,
                 self._method, None, _determine_deadline(deadline), metadata,
-                None if credentials is None else credentials._credentials,
-                operationses, event_handler, self._context)
+                None if credentials is None else
+                credentials._credentials, operationses,
+                _event_handler(state,
+                               self._response_deserializer), self._context)
             return _Rendezvous(state, call, self._response_deserializer,
                                deadline)
 
@@ -652,18 +662,19 @@
         self._context = cygrpc.build_census_context()
 
     def _blocking(self, request_iterator, timeout, metadata, credentials,
-                  wait_for_ready):
+                  wait_for_ready, compression):
         deadline = _deadline(timeout)
         state = _RPCState(_STREAM_UNARY_INITIAL_DUE, None, None, None, None)
         initial_metadata_flags = _InitialMetadataFlags().with_wait_for_ready(
             wait_for_ready)
-        deadline_to_propagate = _determine_deadline(deadline)
+        augmented_metadata = _compression.augment_metadata(
+            metadata, compression)
         call = self._channel.segregated_call(
             cygrpc.PropagationConstants.GRPC_PROPAGATE_DEFAULTS, self._method,
-            None, deadline_to_propagate, metadata, None
+            None, _determine_deadline(deadline), augmented_metadata, None
             if credentials is None else credentials._credentials,
             _stream_unary_invocation_operationses_and_tags(
-                metadata, initial_metadata_flags), self._context)
+                augmented_metadata, initial_metadata_flags), self._context)
         _consume_request_iterator(request_iterator, state, call,
                                   self._request_serializer, None)
         while True:
@@ -680,9 +691,10 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         state, call, = self._blocking(request_iterator, timeout, metadata,
-                                      credentials, wait_for_ready)
+                                      credentials, wait_for_ready, compression)
         return _end_unary_response_blocking(state, call, False, None)
 
     def with_call(self,
@@ -690,9 +702,10 @@
                   timeout=None,
                   metadata=None,
                   credentials=None,
-                  wait_for_ready=None):
+                  wait_for_ready=None,
+                  compression=None):
         state, call, = self._blocking(request_iterator, timeout, metadata,
-                                      credentials, wait_for_ready)
+                                      credentials, wait_for_ready, compression)
         return _end_unary_response_blocking(state, call, True, None)
 
     def future(self,
@@ -700,15 +713,18 @@
                timeout=None,
                metadata=None,
                credentials=None,
-               wait_for_ready=None):
+               wait_for_ready=None,
+               compression=None):
         deadline = _deadline(timeout)
         state = _RPCState(_STREAM_UNARY_INITIAL_DUE, None, None, None, None)
         event_handler = _event_handler(state, self._response_deserializer)
         initial_metadata_flags = _InitialMetadataFlags().with_wait_for_ready(
             wait_for_ready)
+        augmented_metadata = _compression.augment_metadata(
+            metadata, compression)
         call = self._managed_call(
             cygrpc.PropagationConstants.GRPC_PROPAGATE_DEFAULTS, self._method,
-            None, deadline, metadata, None
+            None, deadline, augmented_metadata, None
             if credentials is None else credentials._credentials,
             _stream_unary_invocation_operationses(
                 metadata, initial_metadata_flags), event_handler, self._context)
@@ -734,24 +750,26 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         deadline = _deadline(timeout)
         state = _RPCState(_STREAM_STREAM_INITIAL_DUE, None, None, None, None)
         initial_metadata_flags = _InitialMetadataFlags().with_wait_for_ready(
             wait_for_ready)
+        augmented_metadata = _compression.augment_metadata(
+            metadata, compression)
         operationses = (
             (
-                cygrpc.SendInitialMetadataOperation(metadata,
+                cygrpc.SendInitialMetadataOperation(augmented_metadata,
                                                     initial_metadata_flags),
                 cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS),
             ),
             (cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),),
         )
         event_handler = _event_handler(state, self._response_deserializer)
-        deadline_to_propagate = _determine_deadline(deadline)
         call = self._managed_call(
             cygrpc.PropagationConstants.GRPC_PROPAGATE_DEFAULTS, self._method,
-            None, deadline_to_propagate, metadata, None
+            None, _determine_deadline(deadline), augmented_metadata, None
             if credentials is None else credentials._credentials, operationses,
             event_handler, self._context)
         _consume_request_iterator(request_iterator, state, call,
@@ -982,28 +1000,30 @@
                 break
 
 
-def _options(options):
-    return list(options) + [
-        (
-            cygrpc.ChannelArgKey.primary_user_agent_string,
-            _USER_AGENT,
-        ),
-    ]
+def _augment_options(base_options, compression):
+    compression_option = _compression.create_channel_option(compression)
+    return tuple(base_options) + compression_option + ((
+        cygrpc.ChannelArgKey.primary_user_agent_string,
+        _USER_AGENT,
+    ),)
 
 
 class Channel(grpc.Channel):
     """A cygrpc.Channel-backed implementation of grpc.Channel."""
 
-    def __init__(self, target, options, credentials):
+    def __init__(self, target, options, credentials, compression):
         """Constructor.
 
         Args:
           target: The target to which to connect.
           options: Configuration options for the channel.
           credentials: A cygrpc.ChannelCredentials or None.
+          compression: An optional value indicating the compression method to be
+            used over the lifetime of the channel.
         """
         self._channel = cygrpc.Channel(
-            _common.encode(target), _options(options), credentials)
+            _common.encode(target), _augment_options(options, compression),
+            credentials)
         self._call_state = _ChannelCallState(self._channel)
         self._connectivity_state = _ChannelConnectivityState(self._channel)
         cygrpc.fork_register_channel(self)
diff --git a/src/python/grpcio/grpc/_compression.py b/src/python/grpcio/grpc/_compression.py
new file mode 100644
index 0000000..45339c3
--- /dev/null
+++ b/src/python/grpcio/grpc/_compression.py
@@ -0,0 +1,55 @@
+# Copyright 2019 The 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.
+
+from grpc._cython import cygrpc
+
+NoCompression = cygrpc.CompressionAlgorithm.none
+Deflate = cygrpc.CompressionAlgorithm.deflate
+Gzip = cygrpc.CompressionAlgorithm.gzip
+
+_METADATA_STRING_MAPPING = {
+    NoCompression: 'identity',
+    Deflate: 'deflate',
+    Gzip: 'gzip',
+}
+
+
+def _compression_algorithm_to_metadata_value(compression):
+    return _METADATA_STRING_MAPPING[compression]
+
+
+def compression_algorithm_to_metadata(compression):
+    return (cygrpc.GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY,
+            _compression_algorithm_to_metadata_value(compression))
+
+
+def create_channel_option(compression):
+    return ((cygrpc.GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM,
+             int(compression)),) if compression else ()
+
+
+def augment_metadata(metadata, compression):
+    if not metadata and not compression:
+        return None
+    base_metadata = tuple(metadata) if metadata else ()
+    compression_metadata = (
+        compression_algorithm_to_metadata(compression),) if compression else ()
+    return base_metadata + compression_metadata
+
+
+__all__ = (
+    "NoCompression",
+    "Deflate",
+    "Gzip",
+)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi
index 9f06ce0..0307f74 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd.pxi
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 
-cdef grpc_event _next(grpc_completion_queue *c_completion_queue, deadline)
+cdef grpc_event _next(grpc_completion_queue *c_completion_queue, deadline) except *
 
 
 cdef _interpret_event(grpc_event c_event)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi
index 212d27d..325e72a 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi
@@ -20,7 +20,7 @@
 cdef int _INTERRUPT_CHECK_PERIOD_MS = 200
 
 
-cdef grpc_event _next(grpc_completion_queue *c_completion_queue, deadline):
+cdef grpc_event _next(grpc_completion_queue *c_completion_queue, deadline) except *:
   cdef gpr_timespec c_increment
   cdef gpr_timespec c_timeout
   cdef gpr_timespec c_deadline
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
index 0a35002..057d077 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
@@ -140,7 +140,8 @@
   const char *GRPC_ARG_SECONDARY_USER_AGENT_STRING
   const char *GRPC_SSL_TARGET_NAME_OVERRIDE_ARG
   const char *GRPC_SSL_SESSION_CACHE_ARG
-  const char *GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM
+  const char *_GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM \
+    "GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM"
   const char *GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL
   const char *GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET
 
@@ -618,3 +619,8 @@
   int grpc_compression_options_is_algorithm_enabled(
       const grpc_compression_options *opts,
       grpc_compression_algorithm algorithm) nogil
+
+cdef extern from "grpc/impl/codegen/compression_types.h":
+
+  const char *_GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY \
+    "GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY"
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx.pxi
index 02c904b..308d677 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx.pxi
@@ -108,6 +108,11 @@
   receive_status_on_client = GRPC_OP_RECV_STATUS_ON_CLIENT
   receive_close_on_server = GRPC_OP_RECV_CLOSE_ON_SERVER
 
+GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM= (
+  _GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM)
+
+GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY = (
+  _GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY)
 
 class CompressionAlgorithm:
   none = GRPC_COMPRESS_NONE
diff --git a/src/python/grpcio/grpc/_grpcio_metadata.py b/src/python/grpcio/grpc/_grpcio_metadata.py
index 79e68cf..651f8f7 100644
--- a/src/python/grpcio/grpc/_grpcio_metadata.py
+++ b/src/python/grpcio/grpc/_grpcio_metadata.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc/_grpcio_metadata.py.template`!!!
 
-__version__ = """1.20.1"""
+__version__ = """1.21.0.dev0"""
diff --git a/src/python/grpcio/grpc/_interceptor.py b/src/python/grpcio/grpc/_interceptor.py
index 6c4e396..4ec2e6b 100644
--- a/src/python/grpcio/grpc/_interceptor.py
+++ b/src/python/grpcio/grpc/_interceptor.py
@@ -44,9 +44,9 @@
 
 
 class _ClientCallDetails(
-        collections.namedtuple(
-            '_ClientCallDetails',
-            ('method', 'timeout', 'metadata', 'credentials', 'wait_for_ready')),
+        collections.namedtuple('_ClientCallDetails',
+                               ('method', 'timeout', 'metadata', 'credentials',
+                                'wait_for_ready', 'compression')),
         grpc.ClientCallDetails):
     pass
 
@@ -77,7 +77,12 @@
     except AttributeError:
         wait_for_ready = default_details.wait_for_ready
 
-    return method, timeout, metadata, credentials, wait_for_ready
+    try:
+        compression = call_details.compression
+    except AttributeError:
+        compression = default_details.compression
+
+    return method, timeout, metadata, credentials, wait_for_ready, compression
 
 
 class _FailureOutcome(grpc.RpcError, grpc.Future, grpc.Call):  # pylint: disable=too-many-ancestors
@@ -206,13 +211,15 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         response, ignored_call = self._with_call(
             request,
             timeout=timeout,
             metadata=metadata,
             credentials=credentials,
-            wait_for_ready=wait_for_ready)
+            wait_for_ready=wait_for_ready,
+            compression=compression)
         return response
 
     def _with_call(self,
@@ -220,20 +227,25 @@
                    timeout=None,
                    metadata=None,
                    credentials=None,
-                   wait_for_ready=None):
-        client_call_details = _ClientCallDetails(
-            self._method, timeout, metadata, credentials, wait_for_ready)
+                   wait_for_ready=None,
+                   compression=None):
+        client_call_details = _ClientCallDetails(self._method, timeout,
+                                                 metadata, credentials,
+                                                 wait_for_ready, compression)
 
         def continuation(new_details, request):
-            new_method, new_timeout, new_metadata, new_credentials, new_wait_for_ready = (
-                _unwrap_client_call_details(new_details, client_call_details))
+            (new_method, new_timeout, new_metadata, new_credentials,
+             new_wait_for_ready,
+             new_compression) = (_unwrap_client_call_details(
+                 new_details, client_call_details))
             try:
                 response, call = self._thunk(new_method).with_call(
                     request,
                     timeout=new_timeout,
                     metadata=new_metadata,
                     credentials=new_credentials,
-                    wait_for_ready=new_wait_for_ready)
+                    wait_for_ready=new_wait_for_ready,
+                    compression=new_compression)
                 return _UnaryOutcome(response, call)
             except grpc.RpcError as rpc_error:
                 return rpc_error
@@ -249,32 +261,39 @@
                   timeout=None,
                   metadata=None,
                   credentials=None,
-                  wait_for_ready=None):
+                  wait_for_ready=None,
+                  compression=None):
         return self._with_call(
             request,
             timeout=timeout,
             metadata=metadata,
             credentials=credentials,
-            wait_for_ready=wait_for_ready)
+            wait_for_ready=wait_for_ready,
+            compression=compression)
 
     def future(self,
                request,
                timeout=None,
                metadata=None,
                credentials=None,
-               wait_for_ready=None):
-        client_call_details = _ClientCallDetails(
-            self._method, timeout, metadata, credentials, wait_for_ready)
+               wait_for_ready=None,
+               compression=None):
+        client_call_details = _ClientCallDetails(self._method, timeout,
+                                                 metadata, credentials,
+                                                 wait_for_ready, compression)
 
         def continuation(new_details, request):
-            new_method, new_timeout, new_metadata, new_credentials, new_wait_for_ready = (
-                _unwrap_client_call_details(new_details, client_call_details))
+            (new_method, new_timeout, new_metadata, new_credentials,
+             new_wait_for_ready,
+             new_compression) = (_unwrap_client_call_details(
+                 new_details, client_call_details))
             return self._thunk(new_method).future(
                 request,
                 timeout=new_timeout,
                 metadata=new_metadata,
                 credentials=new_credentials,
-                wait_for_ready=new_wait_for_ready)
+                wait_for_ready=new_wait_for_ready,
+                compression=new_compression)
 
         try:
             return self._interceptor.intercept_unary_unary(
@@ -295,19 +314,24 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
-        client_call_details = _ClientCallDetails(
-            self._method, timeout, metadata, credentials, wait_for_ready)
+                 wait_for_ready=None,
+                 compression=None):
+        client_call_details = _ClientCallDetails(self._method, timeout,
+                                                 metadata, credentials,
+                                                 wait_for_ready, compression)
 
         def continuation(new_details, request):
-            new_method, new_timeout, new_metadata, new_credentials, new_wait_for_ready = (
-                _unwrap_client_call_details(new_details, client_call_details))
+            (new_method, new_timeout, new_metadata, new_credentials,
+             new_wait_for_ready,
+             new_compression) = (_unwrap_client_call_details(
+                 new_details, client_call_details))
             return self._thunk(new_method)(
                 request,
                 timeout=new_timeout,
                 metadata=new_metadata,
                 credentials=new_credentials,
-                wait_for_ready=new_wait_for_ready)
+                wait_for_ready=new_wait_for_ready,
+                compression=new_compression)
 
         try:
             return self._interceptor.intercept_unary_stream(
@@ -328,13 +352,15 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
+                 wait_for_ready=None,
+                 compression=None):
         response, ignored_call = self._with_call(
             request_iterator,
             timeout=timeout,
             metadata=metadata,
             credentials=credentials,
-            wait_for_ready=wait_for_ready)
+            wait_for_ready=wait_for_ready,
+            compression=compression)
         return response
 
     def _with_call(self,
@@ -342,20 +368,25 @@
                    timeout=None,
                    metadata=None,
                    credentials=None,
-                   wait_for_ready=None):
-        client_call_details = _ClientCallDetails(
-            self._method, timeout, metadata, credentials, wait_for_ready)
+                   wait_for_ready=None,
+                   compression=None):
+        client_call_details = _ClientCallDetails(self._method, timeout,
+                                                 metadata, credentials,
+                                                 wait_for_ready, compression)
 
         def continuation(new_details, request_iterator):
-            new_method, new_timeout, new_metadata, new_credentials, new_wait_for_ready = (
-                _unwrap_client_call_details(new_details, client_call_details))
+            (new_method, new_timeout, new_metadata, new_credentials,
+             new_wait_for_ready,
+             new_compression) = (_unwrap_client_call_details(
+                 new_details, client_call_details))
             try:
                 response, call = self._thunk(new_method).with_call(
                     request_iterator,
                     timeout=new_timeout,
                     metadata=new_metadata,
                     credentials=new_credentials,
-                    wait_for_ready=new_wait_for_ready)
+                    wait_for_ready=new_wait_for_ready,
+                    compression=new_compression)
                 return _UnaryOutcome(response, call)
             except grpc.RpcError as rpc_error:
                 return rpc_error
@@ -371,32 +402,39 @@
                   timeout=None,
                   metadata=None,
                   credentials=None,
-                  wait_for_ready=None):
+                  wait_for_ready=None,
+                  compression=None):
         return self._with_call(
             request_iterator,
             timeout=timeout,
             metadata=metadata,
             credentials=credentials,
-            wait_for_ready=wait_for_ready)
+            wait_for_ready=wait_for_ready,
+            compression=compression)
 
     def future(self,
                request_iterator,
                timeout=None,
                metadata=None,
                credentials=None,
-               wait_for_ready=None):
-        client_call_details = _ClientCallDetails(
-            self._method, timeout, metadata, credentials, wait_for_ready)
+               wait_for_ready=None,
+               compression=None):
+        client_call_details = _ClientCallDetails(self._method, timeout,
+                                                 metadata, credentials,
+                                                 wait_for_ready, compression)
 
         def continuation(new_details, request_iterator):
-            new_method, new_timeout, new_metadata, new_credentials, new_wait_for_ready = (
-                _unwrap_client_call_details(new_details, client_call_details))
+            (new_method, new_timeout, new_metadata, new_credentials,
+             new_wait_for_ready,
+             new_compression) = (_unwrap_client_call_details(
+                 new_details, client_call_details))
             return self._thunk(new_method).future(
                 request_iterator,
                 timeout=new_timeout,
                 metadata=new_metadata,
                 credentials=new_credentials,
-                wait_for_ready=new_wait_for_ready)
+                wait_for_ready=new_wait_for_ready,
+                compression=new_compression)
 
         try:
             return self._interceptor.intercept_stream_unary(
@@ -417,19 +455,24 @@
                  timeout=None,
                  metadata=None,
                  credentials=None,
-                 wait_for_ready=None):
-        client_call_details = _ClientCallDetails(
-            self._method, timeout, metadata, credentials, wait_for_ready)
+                 wait_for_ready=None,
+                 compression=None):
+        client_call_details = _ClientCallDetails(self._method, timeout,
+                                                 metadata, credentials,
+                                                 wait_for_ready, compression)
 
         def continuation(new_details, request_iterator):
-            new_method, new_timeout, new_metadata, new_credentials, new_wait_for_ready = (
-                _unwrap_client_call_details(new_details, client_call_details))
+            (new_method, new_timeout, new_metadata, new_credentials,
+             new_wait_for_ready,
+             new_compression) = (_unwrap_client_call_details(
+                 new_details, client_call_details))
             return self._thunk(new_method)(
                 request_iterator,
                 timeout=new_timeout,
                 metadata=new_metadata,
                 credentials=new_credentials,
-                wait_for_ready=new_wait_for_ready)
+                wait_for_ready=new_wait_for_ready,
+                compression=new_compression)
 
         try:
             return self._interceptor.intercept_stream_stream(
diff --git a/src/python/grpcio/grpc/_server.py b/src/python/grpcio/grpc/_server.py
index 90136ae..370c811 100644
--- a/src/python/grpcio/grpc/_server.py
+++ b/src/python/grpcio/grpc/_server.py
@@ -24,6 +24,7 @@
 
 import grpc
 from grpc import _common
+from grpc import _compression
 from grpc import _interceptor
 from grpc._cython import cygrpc
 
@@ -94,6 +95,7 @@
         self.request = None
         self.client = _OPEN
         self.initial_metadata_allowed = True
+        self.compression_algorithm = None
         self.disable_next_compression = False
         self.trailing_metadata = None
         self.code = None
@@ -129,13 +131,33 @@
     return send_status_from_server
 
 
+def _get_initial_metadata(state, metadata):
+    with state.condition:
+        if state.compression_algorithm:
+            compression_metadata = (
+                _compression.compression_algorithm_to_metadata(
+                    state.compression_algorithm),)
+            if metadata is None:
+                return compression_metadata
+            else:
+                return compression_metadata + tuple(metadata)
+        else:
+            return metadata
+
+
+def _get_initial_metadata_operation(state, metadata):
+    operation = cygrpc.SendInitialMetadataOperation(
+        _get_initial_metadata(state, metadata), _EMPTY_FLAGS)
+    return operation
+
+
 def _abort(state, call, code, details):
     if state.client is not _CANCELLED:
         effective_code = _abortion_code(state, code)
         effective_details = details if state.details is None else state.details
         if state.initial_metadata_allowed:
             operations = (
-                cygrpc.SendInitialMetadataOperation(None, _EMPTY_FLAGS),
+                _get_initial_metadata_operation(state, None),
                 cygrpc.SendStatusFromServerOperation(
                     state.trailing_metadata, effective_code, effective_details,
                     _EMPTY_FLAGS),
@@ -259,14 +281,18 @@
                 cygrpc.auth_context(self._rpc_event.call))
         }
 
+    def set_compression(self, compression):
+        with self._state.condition:
+            self._state.compression_algorithm = compression
+
     def send_initial_metadata(self, initial_metadata):
         with self._state.condition:
             if self._state.client is _CANCELLED:
                 _raise_rpc_error(self._state)
             else:
                 if self._state.initial_metadata_allowed:
-                    operation = cygrpc.SendInitialMetadataOperation(
-                        initial_metadata, _EMPTY_FLAGS)
+                    operation = _get_initial_metadata_operation(
+                        self._state, initial_metadata)
                     self._rpc_event.call.start_server_batch(
                         (operation,), _send_initial_metadata(self._state))
                     self._state.initial_metadata_allowed = False
@@ -400,10 +426,13 @@
     with _create_servicer_context(rpc_event, state,
                                   request_deserializer) as context:
         try:
+            response_or_iterator = None
             if send_response_callback is not None:
-                return behavior(argument, context, send_response_callback), True
+                response_or_iterator = behavior(argument, context,
+                                                send_response_callback)
             else:
-                return behavior(argument, context), True
+                response_or_iterator = behavior(argument, context)
+            return response_or_iterator, True
         except Exception as exception:  # pylint: disable=broad-except
             with state.condition:
                 if state.aborted:
@@ -447,6 +476,18 @@
         return serialized_response
 
 
+def _get_send_message_op_flags_from_state(state):
+    if state.disable_next_compression:
+        return cygrpc.WriteFlag.no_compress
+    else:
+        return _EMPTY_FLAGS
+
+
+def _reset_per_message_state(state):
+    with state.condition:
+        state.disable_next_compression = False
+
+
 def _send_response(rpc_event, state, serialized_response):
     with state.condition:
         if not _is_rpc_state_active(state):
@@ -454,19 +495,22 @@
         else:
             if state.initial_metadata_allowed:
                 operations = (
-                    cygrpc.SendInitialMetadataOperation(None, _EMPTY_FLAGS),
-                    cygrpc.SendMessageOperation(serialized_response,
-                                                _EMPTY_FLAGS),
+                    _get_initial_metadata_operation(state, None),
+                    cygrpc.SendMessageOperation(
+                        serialized_response,
+                        _get_send_message_op_flags_from_state(state)),
                 )
                 state.initial_metadata_allowed = False
                 token = _SEND_INITIAL_METADATA_AND_SEND_MESSAGE_TOKEN
             else:
                 operations = (cygrpc.SendMessageOperation(
-                    serialized_response, _EMPTY_FLAGS),)
+                    serialized_response,
+                    _get_send_message_op_flags_from_state(state)),)
                 token = _SEND_MESSAGE_TOKEN
             rpc_event.call.start_server_batch(operations,
                                               _send_message(state, token))
             state.due.add(token)
+            _reset_per_message_state(state)
             while True:
                 state.condition.wait()
                 if token not in state.due:
@@ -483,16 +527,17 @@
                     state.trailing_metadata, code, details, _EMPTY_FLAGS),
             ]
             if state.initial_metadata_allowed:
-                operations.append(
-                    cygrpc.SendInitialMetadataOperation(None, _EMPTY_FLAGS))
+                operations.append(_get_initial_metadata_operation(state, None))
             if serialized_response is not None:
                 operations.append(
-                    cygrpc.SendMessageOperation(serialized_response,
-                                                _EMPTY_FLAGS))
+                    cygrpc.SendMessageOperation(
+                        serialized_response,
+                        _get_send_message_op_flags_from_state(state)))
             rpc_event.call.start_server_batch(
                 operations,
                 _send_status_from_server(state, _SEND_STATUS_FROM_SERVER_TOKEN))
             state.statused = True
+            _reset_per_message_state(state)
             state.due.add(_SEND_STATUS_FROM_SERVER_TOKEN)
 
 
@@ -639,13 +684,13 @@
 
 
 def _reject_rpc(rpc_event, status, details):
+    rpc_state = _RPCState()
     operations = (
-        cygrpc.SendInitialMetadataOperation(None, _EMPTY_FLAGS),
+        _get_initial_metadata_operation(rpc_state, None),
         cygrpc.ReceiveCloseOnServerOperation(_EMPTY_FLAGS),
         cygrpc.SendStatusFromServerOperation(None, status, details,
                                              _EMPTY_FLAGS),
     )
-    rpc_state = _RPCState()
     rpc_event.call.start_server_batch(operations,
                                       lambda ignored_event: (rpc_state, (),))
     return rpc_state
@@ -883,13 +928,18 @@
                 'not have "service" method!'.format(generic_rpc_handler))
 
 
+def _augment_options(base_options, compression):
+    compression_option = _compression.create_channel_option(compression)
+    return tuple(base_options) + compression_option
+
+
 class _Server(grpc.Server):
 
     # pylint: disable=too-many-arguments
     def __init__(self, thread_pool, generic_handlers, interceptors, options,
-                 maximum_concurrent_rpcs):
+                 maximum_concurrent_rpcs, compression):
         completion_queue = cygrpc.CompletionQueue()
-        server = cygrpc.Server(options)
+        server = cygrpc.Server(_augment_options(options, compression))
         server.register_completion_queue(completion_queue)
         self._state = _ServerState(completion_queue, server, generic_handlers,
                                    _interceptor.service_pipeline(interceptors),
@@ -920,7 +970,7 @@
 
 
 def create_server(thread_pool, generic_rpc_handlers, interceptors, options,
-                  maximum_concurrent_rpcs):
+                  maximum_concurrent_rpcs, compression):
     _validate_generic_rpc_handlers(generic_rpc_handlers)
     return _Server(thread_pool, generic_rpc_handlers, interceptors, options,
-                   maximum_concurrent_rpcs)
+                   maximum_concurrent_rpcs, compression)
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 21efd68..61524eb 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -19,7 +19,6 @@
     'third_party/address_sorting/address_sorting_posix.c',
     'third_party/address_sorting/address_sorting_windows.c',
     'src/core/lib/gpr/alloc.cc',
-    'src/core/lib/gpr/arena.cc',
     'src/core/lib/gpr/atm.cc',
     'src/core/lib/gpr/cpu_iphone.cc',
     'src/core/lib/gpr/cpu_linux.cc',
@@ -52,7 +51,9 @@
     'src/core/lib/gpr/tmpfile_posix.cc',
     'src/core/lib/gpr/tmpfile_windows.cc',
     'src/core/lib/gpr/wrap_memcpy.cc',
+    'src/core/lib/gprpp/arena.cc',
     'src/core/lib/gprpp/fork.cc',
+    'src/core/lib/gprpp/global_config_env.cc',
     'src/core/lib/gprpp/thd_posix.cc',
     'src/core/lib/gprpp/thd_windows.cc',
     'src/core/lib/profiling/basic_timers.cc',
@@ -71,6 +72,7 @@
     'src/core/lib/channel/handshaker_registry.cc',
     'src/core/lib/channel/status_util.cc',
     'src/core/lib/compression/compression.cc',
+    'src/core/lib/compression/compression_args.cc',
     'src/core/lib/compression/compression_internal.cc',
     'src/core/lib/compression/message_compress.cc',
     'src/core/lib/compression/stream_compression.cc',
@@ -83,12 +85,15 @@
     'src/core/lib/http/parser.cc',
     'src/core/lib/iomgr/buffer_list.cc',
     'src/core/lib/iomgr/call_combiner.cc',
+    'src/core/lib/iomgr/cfstream_handle.cc',
     'src/core/lib/iomgr/combiner.cc',
     'src/core/lib/iomgr/endpoint.cc',
+    'src/core/lib/iomgr/endpoint_cfstream.cc',
     'src/core/lib/iomgr/endpoint_pair_posix.cc',
     'src/core/lib/iomgr/endpoint_pair_uv.cc',
     'src/core/lib/iomgr/endpoint_pair_windows.cc',
     'src/core/lib/iomgr/error.cc',
+    'src/core/lib/iomgr/error_cfstream.cc',
     'src/core/lib/iomgr/ev_epoll1_linux.cc',
     'src/core/lib/iomgr/ev_epollex_linux.cc',
     'src/core/lib/iomgr/ev_poll_posix.cc',
@@ -109,6 +114,7 @@
     'src/core/lib/iomgr/iomgr_custom.cc',
     'src/core/lib/iomgr/iomgr_internal.cc',
     'src/core/lib/iomgr/iomgr_posix.cc',
+    'src/core/lib/iomgr/iomgr_posix_cfstream.cc',
     'src/core/lib/iomgr/iomgr_uv.cc',
     'src/core/lib/iomgr/iomgr_windows.cc',
     'src/core/lib/iomgr/is_epollexclusive_available.cc',
@@ -137,6 +143,7 @@
     'src/core/lib/iomgr/socket_utils_windows.cc',
     'src/core/lib/iomgr/socket_windows.cc',
     'src/core/lib/iomgr/tcp_client.cc',
+    'src/core/lib/iomgr/tcp_client_cfstream.cc',
     'src/core/lib/iomgr/tcp_client_custom.cc',
     'src/core/lib/iomgr/tcp_client_posix.cc',
     'src/core/lib/iomgr/tcp_client_windows.cc',
@@ -370,12 +377,16 @@
     'src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc',
+    'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc',
+    'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc',
+    'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc',
     'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc',
+    'src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc',
     'src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc',
     'src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc',
     'src/core/ext/filters/census/grpc_context.cc',
diff --git a/src/python/grpcio/grpc_version.py b/src/python/grpcio/grpc_version.py
index 443a2dc..e430cc2 100644
--- a/src/python/grpcio/grpc_version.py
+++ b/src/python/grpcio/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel b/src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel
index aae8ced..eccc166 100644
--- a/src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel
+++ b/src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel
@@ -1,30 +1,10 @@
-load("@grpc_python_dependencies//:requirements.bzl", "requirement")
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
-
+load("//bazel:python_rules.bzl", "py_proto_library")
 package(default_visibility = ["//visibility:public"])
 
-genrule(
-    name = "mv_channelz_proto",
-    srcs = [
-        "//src/proto/grpc/channelz:channelz_proto_file",
-    ],
-    outs = ["channelz.proto",],
-    cmd = "cp $< $@",
-)
-
 py_proto_library(
     name = "py_channelz_proto",
-    protos = ["mv_channelz_proto",],
-    imports = [
-        "external/com_google_protobuf/src/",
-    ],
-    inputs = [
-        "@com_google_protobuf//:well_known_protos",
-    ],
-    with_grpc = True,
-    deps = [
-        requirement('protobuf'),
-    ],
+    well_known_protos = True,
+    deps = ["//src/proto/grpc/channelz:channelz_proto_descriptors"],
 )
 
 py_library(
diff --git a/src/python/grpcio_channelz/grpc_version.py b/src/python/grpcio_channelz/grpc_version.py
index a10aa78..d716b95 100644
--- a/src/python/grpcio_channelz/grpc_version.py
+++ b/src/python/grpcio_channelz/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_channelz/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/src/python/grpcio_health_checking/grpc_health/v1/BUILD.bazel b/src/python/grpcio_health_checking/grpc_health/v1/BUILD.bazel
index ce3121f..9e4fff3 100644
--- a/src/python/grpcio_health_checking/grpc_health/v1/BUILD.bazel
+++ b/src/python/grpcio_health_checking/grpc_health/v1/BUILD.bazel
@@ -1,24 +1,9 @@
-load("@grpc_python_dependencies//:requirements.bzl", "requirement")
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
-
+load("//bazel:python_rules.bzl", "py_proto_library")
 package(default_visibility = ["//visibility:public"])
 
-genrule(
-    name = "mv_health_proto",
-    srcs = [
-        "//src/proto/grpc/health/v1:health_proto_file",
-    ],
-    outs = ["health.proto",],
-    cmd = "cp $< $@",
-)
-
 py_proto_library(
     name = "py_health_proto",
-    protos = [":mv_health_proto",],
-    with_grpc = True,
-    deps = [
-        requirement('protobuf'),
-    ],
+    deps = ["//src/proto/grpc/health/v1:health_proto_descriptor",],
 )
 
 py_library(
diff --git a/src/python/grpcio_health_checking/grpc_version.py b/src/python/grpcio_health_checking/grpc_version.py
index 7501427..2bb47aa 100644
--- a/src/python/grpcio_health_checking/grpc_version.py
+++ b/src/python/grpcio_health_checking/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/src/python/grpcio_reflection/grpc_reflection/v1alpha/BUILD.bazel b/src/python/grpcio_reflection/grpc_reflection/v1alpha/BUILD.bazel
index 3a2ba26..6aaa4dd 100644
--- a/src/python/grpcio_reflection/grpc_reflection/v1alpha/BUILD.bazel
+++ b/src/python/grpcio_reflection/grpc_reflection/v1alpha/BUILD.bazel
@@ -1,24 +1,11 @@
+load("//bazel:python_rules.bzl", "py_proto_library")
 load("@grpc_python_dependencies//:requirements.bzl", "requirement")
-load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
 
 package(default_visibility = ["//visibility:public"])
 
-genrule(
-    name = "mv_reflection_proto",
-    srcs = [
-        "//src/proto/grpc/reflection/v1alpha:reflection_proto_file",
-    ],
-    outs = ["reflection.proto",],
-    cmd = "cp $< $@",
-)
-
 py_proto_library(
     name = "py_reflection_proto",
-    protos = [":mv_reflection_proto",],
-    with_grpc = True,
-    deps = [
-        requirement('protobuf'),
-    ],
+    deps = ["//src/proto/grpc/reflection/v1alpha:reflection_proto_descriptor",],
 )
 
 py_library(
diff --git a/src/python/grpcio_reflection/grpc_version.py b/src/python/grpcio_reflection/grpc_version.py
index 64ec549..e1c4f3d 100644
--- a/src/python/grpcio_reflection/grpc_version.py
+++ b/src/python/grpcio_reflection/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_reflection/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/src/python/grpcio_status/grpc_version.py b/src/python/grpcio_status/grpc_version.py
index 456fcde..b484a7b 100644
--- a/src/python/grpcio_status/grpc_version.py
+++ b/src/python/grpcio_status/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_status/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/src/python/grpcio_testing/grpc_testing/_server/_servicer_context.py b/src/python/grpcio_testing/grpc_testing/_server/_servicer_context.py
index 5b1dfea..63a1b1a 100644
--- a/src/python/grpcio_testing/grpc_testing/_server/_servicer_context.py
+++ b/src/python/grpcio_testing/grpc_testing/_server/_servicer_context.py
@@ -56,6 +56,9 @@
     def auth_context(self):
         raise NotImplementedError()
 
+    def set_compression(self):
+        raise NotImplementedError()
+
     def send_initial_metadata(self, initial_metadata):
         initial_metadata_sent = self._rpc.send_initial_metadata(
             _common.fuss_with_metadata(initial_metadata))
@@ -63,6 +66,9 @@
             raise ValueError(
                 'ServicerContext.send_initial_metadata called too late!')
 
+    def disable_next_message_compression(self):
+        raise NotImplementedError()
+
     def set_trailing_metadata(self, trailing_metadata):
         self._rpc.set_trailing_metadata(
             _common.fuss_with_metadata(trailing_metadata))
diff --git a/src/python/grpcio_testing/grpc_version.py b/src/python/grpcio_testing/grpc_version.py
index ed767b6..21981ee 100644
--- a/src/python/grpcio_testing/grpc_version.py
+++ b/src/python/grpcio_testing/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_testing/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/src/python/grpcio_tests/commands.py b/src/python/grpcio_tests/commands.py
index 8f27ab5..dc0795d 100644
--- a/src/python/grpcio_tests/commands.py
+++ b/src/python/grpcio_tests/commands.py
@@ -117,6 +117,7 @@
         # eventually succeed, but need to dig into performance issues.
         'unit._cython._no_messages_server_completion_queue_per_call_test.Test.test_rpcs',
         'unit._cython._no_messages_single_server_completion_queue_test.Test.test_rpcs',
+        'unit._compression_test',
         # TODO(https://github.com/grpc/grpc/issues/16890) enable this test
         'unit._cython._channel_test.ChannelTest.test_multiple_channels_lonely_connectivity',
         # I have no idea why this doesn't work in gevent, but it shouldn't even be
diff --git a/src/python/grpcio_tests/grpc_version.py b/src/python/grpcio_tests/grpc_version.py
index 8c8ee0c..8ce4fdb 100644
--- a/src/python/grpcio_tests/grpc_version.py
+++ b/src/python/grpcio_tests/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_tests/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/src/python/grpcio_tests/tests/fork/_fork_interop_test.py b/src/python/grpcio_tests/tests/fork/_fork_interop_test.py
index 608148d..602786c 100644
--- a/src/python/grpcio_tests/tests/fork/_fork_interop_test.py
+++ b/src/python/grpcio_tests/tests/fork/_fork_interop_test.py
@@ -56,12 +56,12 @@
 
             import grpc
             from src.proto.grpc.testing import test_pb2_grpc
-            from tests.interop import methods as interop_methods
+            from tests.interop import service as interop_service
             from tests.unit import test_common
 
             server = test_common.test_server()
             test_pb2_grpc.add_TestServiceServicer_to_server(
-                interop_methods.TestService(), server)
+                interop_service.TestService(), server)
             port = server.add_insecure_port('[::]:0')
             server.start()
             print(port)
diff --git a/src/python/grpcio_tests/tests/health_check/_health_servicer_test.py b/src/python/grpcio_tests/tests/health_check/_health_servicer_test.py
index 1098d38..7a332b8 100644
--- a/src/python/grpcio_tests/tests/health_check/_health_servicer_test.py
+++ b/src/python/grpcio_tests/tests/health_check/_health_servicer_test.py
@@ -246,7 +246,7 @@
         resp = self._stub.Check(request)
         self.assertEqual(health_pb2.HealthCheckResponse.SERVING, resp.status)
 
-    def test_check_unknown_serivce(self):
+    def test_check_unknown_service(self):
         request = health_pb2.HealthCheckRequest(service=_UNKNOWN_SERVICE)
         resp = self._stub.Check(request)
         self.assertEqual(health_pb2.HealthCheckResponse.UNKNOWN, resp.status)
diff --git a/src/python/grpcio_tests/tests/interop/BUILD.bazel b/src/python/grpcio_tests/tests/interop/BUILD.bazel
index 770b1f7..fd63655 100644
--- a/src/python/grpcio_tests/tests/interop/BUILD.bazel
+++ b/src/python/grpcio_tests/tests/interop/BUILD.bazel
@@ -5,45 +5,45 @@
 py_library(
     name = "_intraop_test_case",
     srcs = ["_intraop_test_case.py"],
+    imports = ["../../"],
     deps = [
         ":methods",
     ],
-    imports=["../../",],
 )
 
 py_library(
     name = "client",
     srcs = ["client.py"],
+    imports = ["../../"],
     deps = [
-        "//src/python/grpcio/grpc:grpcio",
         ":methods",
         ":resources",
         "//src/proto/grpc/testing:py_test_proto",
-        requirement('google-auth'),
+        "//src/python/grpcio/grpc:grpcio",
+        requirement("google-auth"),
     ],
-    imports=["../../",],
 )
 
 py_library(
     name = "methods",
     srcs = ["methods.py"],
+    imports = ["../../"],
     deps = [
         "//src/python/grpcio/grpc:grpcio",
         "//src/python/grpcio_tests/tests:bazel_namespace_package_hack",
         "//src/proto/grpc/testing:py_empty_proto",
         "//src/proto/grpc/testing:py_messages_proto",
         "//src/proto/grpc/testing:py_test_proto",
-        requirement('google-auth'),
-        requirement('requests'),
-        requirement('urllib3'),
-        requirement('chardet'),
-        requirement('certifi'),
-        requirement('idna'),
+        requirement("google-auth"),
+        requirement("requests"),
+        requirement("urllib3"),
+        requirement("chardet"),
+        requirement("certifi"),
+        requirement("idna"),
     ] + select({
-        "//conditions:default": [requirement('enum34'),],
+        "//conditions:default": [requirement("enum34")],
         "//:python3": [],
     }),
-    imports=["../../",],
 )
 
 py_library(
@@ -55,50 +55,61 @@
 )
 
 py_library(
+    name = "service",
+    srcs = ["service.py"],
+    imports = ["../../"],
+    deps = [
+        "//src/proto/grpc/testing:py_empty_proto",
+        "//src/proto/grpc/testing:py_messages_proto",
+        "//src/proto/grpc/testing:py_test_proto",
+        "//src/python/grpcio/grpc:grpcio",
+    ],
+)
+
+py_library(
     name = "server",
     srcs = ["server.py"],
+    imports = ["../../"],
     deps = [
-        "//src/python/grpcio/grpc:grpcio",
-        ":methods",
         ":resources",
-        "//src/python/grpcio_tests/tests/unit:test_common",
+        ":service",
         "//src/proto/grpc/testing:py_test_proto",
+        "//src/python/grpcio/grpc:grpcio",
+        "//src/python/grpcio_tests/tests/unit:test_common",
     ],
-    imports=["../../",],
 )
 
 py_test(
-    name="_insecure_intraop_test",
-    size="small",
-    srcs=["_insecure_intraop_test.py",],
-    main="_insecure_intraop_test.py",
-    deps=[
-        "//src/python/grpcio/grpc:grpcio",
-        ":_intraop_test_case",
-        ":methods",
-        ":server",
-        "//src/python/grpcio_tests/tests/unit:test_common",
-        "//src/proto/grpc/testing:py_test_proto",
-    ],
-    imports=["../../",],
-    data=[
+    name = "_insecure_intraop_test",
+    size = "small",
+    srcs = ["_insecure_intraop_test.py"],
+    data = [
         "//src/python/grpcio_tests/tests/unit/credentials",
     ],
+    imports = ["../../"],
+    main = "_insecure_intraop_test.py",
+    deps = [
+        ":_intraop_test_case",
+        ":server",
+        ":service",
+        "//src/proto/grpc/testing:py_test_proto",
+        "//src/python/grpcio/grpc:grpcio",
+        "//src/python/grpcio_tests/tests/unit:test_common",
+    ],
 )
 
 py_test(
-    name="_secure_intraop_test",
-    size="small",
-    srcs=["_secure_intraop_test.py",],
-    main="_secure_intraop_test.py",
-    deps=[
-        "//src/python/grpcio/grpc:grpcio",
+    name = "_secure_intraop_test",
+    size = "small",
+    srcs = ["_secure_intraop_test.py"],
+    imports = ["../../"],
+    main = "_secure_intraop_test.py",
+    deps = [
         ":_intraop_test_case",
-        ":methods",
         ":server",
-        "//src/python/grpcio_tests/tests/unit:test_common",
+        ":service",
         "//src/proto/grpc/testing:py_test_proto",
+        "//src/python/grpcio/grpc:grpcio",
+        "//src/python/grpcio_tests/tests/unit:test_common",
     ],
-    imports=["../../",],
 )
-
diff --git a/src/python/grpcio_tests/tests/interop/_insecure_intraop_test.py b/src/python/grpcio_tests/tests/interop/_insecure_intraop_test.py
index ace15be..fecf317 100644
--- a/src/python/grpcio_tests/tests/interop/_insecure_intraop_test.py
+++ b/src/python/grpcio_tests/tests/interop/_insecure_intraop_test.py
@@ -19,7 +19,7 @@
 from src.proto.grpc.testing import test_pb2_grpc
 
 from tests.interop import _intraop_test_case
-from tests.interop import methods
+from tests.interop import service
 from tests.interop import server
 from tests.unit import test_common
 
@@ -29,7 +29,7 @@
 
     def setUp(self):
         self.server = test_common.test_server()
-        test_pb2_grpc.add_TestServiceServicer_to_server(methods.TestService(),
+        test_pb2_grpc.add_TestServiceServicer_to_server(service.TestService(),
                                                         self.server)
         port = self.server.add_insecure_port('[::]:0')
         self.server.start()
diff --git a/src/python/grpcio_tests/tests/interop/_secure_intraop_test.py b/src/python/grpcio_tests/tests/interop/_secure_intraop_test.py
index e27e551..1b5e5cf 100644
--- a/src/python/grpcio_tests/tests/interop/_secure_intraop_test.py
+++ b/src/python/grpcio_tests/tests/interop/_secure_intraop_test.py
@@ -19,7 +19,7 @@
 from src.proto.grpc.testing import test_pb2_grpc
 
 from tests.interop import _intraop_test_case
-from tests.interop import methods
+from tests.interop import service
 from tests.interop import resources
 from tests.unit import test_common
 
@@ -30,7 +30,7 @@
 
     def setUp(self):
         self.server = test_common.test_server()
-        test_pb2_grpc.add_TestServiceServicer_to_server(methods.TestService(),
+        test_pb2_grpc.add_TestServiceServicer_to_server(service.TestService(),
                                                         self.server)
         port = self.server.add_secure_port(
             '[::]:0',
diff --git a/src/python/grpcio_tests/tests/interop/methods.py b/src/python/grpcio_tests/tests/interop/methods.py
index 40341ca..250db8b 100644
--- a/src/python/grpcio_tests/tests/interop/methods.py
+++ b/src/python/grpcio_tests/tests/interop/methods.py
@@ -25,6 +25,7 @@
 import json
 import os
 import threading
+import time
 
 from google import auth as google_auth
 from google.auth import environment_vars as google_auth_environment_vars
@@ -34,78 +35,11 @@
 
 from src.proto.grpc.testing import empty_pb2
 from src.proto.grpc.testing import messages_pb2
-from src.proto.grpc.testing import test_pb2_grpc
 
 _INITIAL_METADATA_KEY = "x-grpc-test-echo-initial"
 _TRAILING_METADATA_KEY = "x-grpc-test-echo-trailing-bin"
 
 
-def _maybe_echo_metadata(servicer_context):
-    """Copies metadata from request to response if it is present."""
-    invocation_metadata = dict(servicer_context.invocation_metadata())
-    if _INITIAL_METADATA_KEY in invocation_metadata:
-        initial_metadatum = (_INITIAL_METADATA_KEY,
-                             invocation_metadata[_INITIAL_METADATA_KEY])
-        servicer_context.send_initial_metadata((initial_metadatum,))
-    if _TRAILING_METADATA_KEY in invocation_metadata:
-        trailing_metadatum = (_TRAILING_METADATA_KEY,
-                              invocation_metadata[_TRAILING_METADATA_KEY])
-        servicer_context.set_trailing_metadata((trailing_metadatum,))
-
-
-def _maybe_echo_status_and_message(request, servicer_context):
-    """Sets the response context code and details if the request asks for them"""
-    if request.HasField('response_status'):
-        servicer_context.set_code(request.response_status.code)
-        servicer_context.set_details(request.response_status.message)
-
-
-class TestService(test_pb2_grpc.TestServiceServicer):
-
-    def EmptyCall(self, request, context):
-        _maybe_echo_metadata(context)
-        return empty_pb2.Empty()
-
-    def UnaryCall(self, request, context):
-        _maybe_echo_metadata(context)
-        _maybe_echo_status_and_message(request, context)
-        return messages_pb2.SimpleResponse(
-            payload=messages_pb2.Payload(
-                type=messages_pb2.COMPRESSABLE,
-                body=b'\x00' * request.response_size))
-
-    def StreamingOutputCall(self, request, context):
-        _maybe_echo_status_and_message(request, context)
-        for response_parameters in request.response_parameters:
-            yield messages_pb2.StreamingOutputCallResponse(
-                payload=messages_pb2.Payload(
-                    type=request.response_type,
-                    body=b'\x00' * response_parameters.size))
-
-    def StreamingInputCall(self, request_iterator, context):
-        aggregate_size = 0
-        for request in request_iterator:
-            if request.payload is not None and request.payload.body:
-                aggregate_size += len(request.payload.body)
-        return messages_pb2.StreamingInputCallResponse(
-            aggregated_payload_size=aggregate_size)
-
-    def FullDuplexCall(self, request_iterator, context):
-        _maybe_echo_metadata(context)
-        for request in request_iterator:
-            _maybe_echo_status_and_message(request, context)
-            for response_parameters in request.response_parameters:
-                yield messages_pb2.StreamingOutputCallResponse(
-                    payload=messages_pb2.Payload(
-                        type=request.payload.type,
-                        body=b'\x00' * response_parameters.size))
-
-    # NOTE(nathaniel): Apparently this is the same as the full-duplex call?
-    # NOTE(atash): It isn't even called in the interop spec (Oct 22 2015)...
-    def HalfDuplexCall(self, request_iterator, context):
-        return self.FullDuplexCall(request_iterator, context)
-
-
 def _expect_status_code(call, expected_code):
     if call.code() != expected_code:
         raise ValueError('expected code %s, got %s' % (expected_code,
diff --git a/src/python/grpcio_tests/tests/interop/server.py b/src/python/grpcio_tests/tests/interop/server.py
index 72f88a1..3611ffd 100644
--- a/src/python/grpcio_tests/tests/interop/server.py
+++ b/src/python/grpcio_tests/tests/interop/server.py
@@ -21,7 +21,7 @@
 import grpc
 from src.proto.grpc.testing import test_pb2_grpc
 
-from tests.interop import methods
+from tests.interop import service
 from tests.interop import resources
 from tests.unit import test_common
 
@@ -42,7 +42,7 @@
     args = parser.parse_args()
 
     server = test_common.test_server()
-    test_pb2_grpc.add_TestServiceServicer_to_server(methods.TestService(),
+    test_pb2_grpc.add_TestServiceServicer_to_server(service.TestService(),
                                                     server)
     if args.use_tls:
         private_key = resources.private_key()
diff --git a/src/python/grpcio_tests/tests/interop/service.py b/src/python/grpcio_tests/tests/interop/service.py
new file mode 100644
index 0000000..37e4404
--- /dev/null
+++ b/src/python/grpcio_tests/tests/interop/service.py
@@ -0,0 +1,97 @@
+# Copyright 2019 The 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.
+"""The Python implementation of the TestServicer."""
+
+import time
+
+import grpc
+
+from src.proto.grpc.testing import empty_pb2
+from src.proto.grpc.testing import messages_pb2
+from src.proto.grpc.testing import test_pb2_grpc
+
+_INITIAL_METADATA_KEY = "x-grpc-test-echo-initial"
+_TRAILING_METADATA_KEY = "x-grpc-test-echo-trailing-bin"
+_US_IN_A_SECOND = 1000 * 1000
+
+
+def _maybe_echo_metadata(servicer_context):
+    """Copies metadata from request to response if it is present."""
+    invocation_metadata = dict(servicer_context.invocation_metadata())
+    if _INITIAL_METADATA_KEY in invocation_metadata:
+        initial_metadatum = (_INITIAL_METADATA_KEY,
+                             invocation_metadata[_INITIAL_METADATA_KEY])
+        servicer_context.send_initial_metadata((initial_metadatum,))
+    if _TRAILING_METADATA_KEY in invocation_metadata:
+        trailing_metadatum = (_TRAILING_METADATA_KEY,
+                              invocation_metadata[_TRAILING_METADATA_KEY])
+        servicer_context.set_trailing_metadata((trailing_metadatum,))
+
+
+def _maybe_echo_status_and_message(request, servicer_context):
+    """Sets the response context code and details if the request asks for them"""
+    if request.HasField('response_status'):
+        servicer_context.set_code(request.response_status.code)
+        servicer_context.set_details(request.response_status.message)
+
+
+class TestService(test_pb2_grpc.TestServiceServicer):
+
+    def EmptyCall(self, request, context):
+        _maybe_echo_metadata(context)
+        return empty_pb2.Empty()
+
+    def UnaryCall(self, request, context):
+        _maybe_echo_metadata(context)
+        _maybe_echo_status_and_message(request, context)
+        return messages_pb2.SimpleResponse(
+            payload=messages_pb2.Payload(
+                type=messages_pb2.COMPRESSABLE,
+                body=b'\x00' * request.response_size))
+
+    def StreamingOutputCall(self, request, context):
+        _maybe_echo_status_and_message(request, context)
+        for response_parameters in request.response_parameters:
+            if response_parameters.interval_us != 0:
+                time.sleep(response_parameters.interval_us / _US_IN_A_SECOND)
+            yield messages_pb2.StreamingOutputCallResponse(
+                payload=messages_pb2.Payload(
+                    type=request.response_type,
+                    body=b'\x00' * response_parameters.size))
+
+    def StreamingInputCall(self, request_iterator, context):
+        aggregate_size = 0
+        for request in request_iterator:
+            if request.payload is not None and request.payload.body:
+                aggregate_size += len(request.payload.body)
+        return messages_pb2.StreamingInputCallResponse(
+            aggregated_payload_size=aggregate_size)
+
+    def FullDuplexCall(self, request_iterator, context):
+        _maybe_echo_metadata(context)
+        for request in request_iterator:
+            _maybe_echo_status_and_message(request, context)
+            for response_parameters in request.response_parameters:
+                if response_parameters.interval_us != 0:
+                    time.sleep(
+                        response_parameters.interval_us / _US_IN_A_SECOND)
+                yield messages_pb2.StreamingOutputCallResponse(
+                    payload=messages_pb2.Payload(
+                        type=request.payload.type,
+                        body=b'\x00' * response_parameters.size))
+
+    # NOTE(nathaniel): Apparently this is the same as the full-duplex call?
+    # NOTE(atash): It isn't even called in the interop spec (Oct 22 2015)...
+    def HalfDuplexCall(self, request_iterator, context):
+        return self.FullDuplexCall(request_iterator, context)
diff --git a/src/python/grpcio_tests/tests/reflection/BUILD.bazel b/src/python/grpcio_tests/tests/reflection/BUILD.bazel
index c0efb0b..b635b72 100644
--- a/src/python/grpcio_tests/tests/reflection/BUILD.bazel
+++ b/src/python/grpcio_tests/tests/reflection/BUILD.bazel
@@ -14,6 +14,7 @@
         "//src/python/grpcio_tests/tests/unit:test_common",
         "//src/proto/grpc/testing:py_empty_proto",
         "//src/proto/grpc/testing/proto2:empty2_extensions_proto",
+        "//src/proto/grpc/testing/proto2:empty2_proto",
         requirement('protobuf'),
     ],
     imports=["../../",],
diff --git a/src/python/grpcio_tests/tests/stress/client.py b/src/python/grpcio_tests/tests/stress/client.py
index 4c35b05..11e46d4 100644
--- a/src/python/grpcio_tests/tests/stress/client.py
+++ b/src/python/grpcio_tests/tests/stress/client.py
@@ -34,12 +34,12 @@
         description='gRPC Python stress test client')
     parser.add_argument(
         '--server_addresses',
-        help='comma seperated list of hostname:port to run servers on',
+        help='comma separated list of hostname:port to run servers on',
         default='localhost:8080',
         type=str)
     parser.add_argument(
         '--test_cases',
-        help='comma seperated list of testcase:weighting of tests to run',
+        help='comma separated list of testcase:weighting of tests to run',
         default='large_unary:100',
         type=str)
     parser.add_argument(
diff --git a/src/python/grpcio_tests/tests/unit/BUILD.bazel b/src/python/grpcio_tests/tests/unit/BUILD.bazel
index 9c9887b..a161794 100644
--- a/src/python/grpcio_tests/tests/unit/BUILD.bazel
+++ b/src/python/grpcio_tests/tests/unit/BUILD.bazel
@@ -35,6 +35,11 @@
 ]
 
 py_library(
+    name = "_tcp_proxy",
+    srcs = ["_tcp_proxy.py"],
+)
+
+py_library(
     name = "resources",
     srcs = ["resources.py"],
     data=[
@@ -81,6 +86,7 @@
             ":_exit_scenarios",
             ":_server_shutdown_scenarios",
             ":_from_grpc_import_star",
+            ":_tcp_proxy",
             "//src/python/grpcio_tests/tests/unit/framework/common",
             "//src/python/grpcio_tests/tests/testing",
             requirement('six'),
diff --git a/src/python/grpcio_tests/tests/unit/_api_test.py b/src/python/grpcio_tests/tests/unit/_api_test.py
index 0dc6a87..127dab3 100644
--- a/src/python/grpcio_tests/tests/unit/_api_test.py
+++ b/src/python/grpcio_tests/tests/unit/_api_test.py
@@ -31,6 +31,7 @@
             'FutureCancelledError',
             'Future',
             'ChannelConnectivity',
+            'Compression',
             'StatusCode',
             'Status',
             'RpcError',
diff --git a/src/python/grpcio_tests/tests/unit/_compression_test.py b/src/python/grpcio_tests/tests/unit/_compression_test.py
index 87884a1..f9cf8eb 100644
--- a/src/python/grpcio_tests/tests/unit/_compression_test.py
+++ b/src/python/grpcio_tests/tests/unit/_compression_test.py
@@ -15,35 +15,124 @@
 
 import unittest
 
+import contextlib
+from concurrent import futures
+import functools
+import itertools
 import logging
+import os
+
 import grpc
 from grpc import _grpcio_metadata
 
 from tests.unit import test_common
 from tests.unit.framework.common import test_constants
+from tests.unit import _tcp_proxy
 
 _UNARY_UNARY = '/test/UnaryUnary'
+_UNARY_STREAM = '/test/UnaryStream'
+_STREAM_UNARY = '/test/StreamUnary'
 _STREAM_STREAM = '/test/StreamStream'
 
+# Cut down on test time.
+_STREAM_LENGTH = test_constants.STREAM_LENGTH // 16
 
-def handle_unary(request, servicer_context):
-    servicer_context.send_initial_metadata([('grpc-internal-encoding-request',
-                                             'gzip')])
-    return request
+_HOST = 'localhost'
+
+_REQUEST = b'\x00' * 100
+_COMPRESSION_RATIO_THRESHOLD = 0.05
+_COMPRESSION_METHODS = (
+    None,
+    # Disabled for test tractability.
+    # grpc.Compression.NoCompression,
+    # grpc.Compression.Deflate,
+    grpc.Compression.Gzip,
+)
+_COMPRESSION_NAMES = {
+    None: 'Uncompressed',
+    grpc.Compression.NoCompression: 'NoCompression',
+    grpc.Compression.Deflate: 'DeflateCompression',
+    grpc.Compression.Gzip: 'GzipCompression',
+}
+
+_TEST_OPTIONS = {
+    'client_streaming': (True, False),
+    'server_streaming': (True, False),
+    'channel_compression': _COMPRESSION_METHODS,
+    'multicallable_compression': _COMPRESSION_METHODS,
+    'server_compression': _COMPRESSION_METHODS,
+    'server_call_compression': _COMPRESSION_METHODS,
+}
 
 
-def handle_stream(request_iterator, servicer_context):
-    # TODO(issue:#6891) We should be able to remove this loop,
-    # and replace with return; yield
-    servicer_context.send_initial_metadata([('grpc-internal-encoding-request',
-                                             'gzip')])
-    for request in request_iterator:
-        yield request
+def _make_handle_unary_unary(pre_response_callback):
+
+    def _handle_unary(request, servicer_context):
+        if pre_response_callback:
+            pre_response_callback(request, servicer_context)
+        return request
+
+    return _handle_unary
+
+
+def _make_handle_unary_stream(pre_response_callback):
+
+    def _handle_unary_stream(request, servicer_context):
+        if pre_response_callback:
+            pre_response_callback(request, servicer_context)
+        for _ in range(_STREAM_LENGTH):
+            yield request
+
+    return _handle_unary_stream
+
+
+def _make_handle_stream_unary(pre_response_callback):
+
+    def _handle_stream_unary(request_iterator, servicer_context):
+        if pre_response_callback:
+            pre_response_callback(request_iterator, servicer_context)
+        response = None
+        for request in request_iterator:
+            if not response:
+                response = request
+        return response
+
+    return _handle_stream_unary
+
+
+def _make_handle_stream_stream(pre_response_callback):
+
+    def _handle_stream(request_iterator, servicer_context):
+        # TODO(issue:#6891) We should be able to remove this loop,
+        # and replace with return; yield
+        for request in request_iterator:
+            if pre_response_callback:
+                pre_response_callback(request, servicer_context)
+            yield request
+
+    return _handle_stream
+
+
+def set_call_compression(compression_method, request_or_iterator,
+                         servicer_context):
+    del request_or_iterator
+    servicer_context.set_compression(compression_method)
+
+
+def disable_next_compression(request, servicer_context):
+    del request
+    servicer_context.disable_next_message_compression()
+
+
+def disable_first_compression(request, servicer_context):
+    if int(request.decode('ascii')) == 0:
+        servicer_context.disable_next_message_compression()
 
 
 class _MethodHandler(grpc.RpcMethodHandler):
 
-    def __init__(self, request_streaming, response_streaming):
+    def __init__(self, request_streaming, response_streaming,
+                 pre_response_callback):
         self.request_streaming = request_streaming
         self.response_streaming = response_streaming
         self.request_deserializer = None
@@ -52,75 +141,239 @@
         self.unary_stream = None
         self.stream_unary = None
         self.stream_stream = None
+
         if self.request_streaming and self.response_streaming:
-            self.stream_stream = handle_stream
+            self.stream_stream = _make_handle_stream_stream(
+                pre_response_callback)
         elif not self.request_streaming and not self.response_streaming:
-            self.unary_unary = handle_unary
+            self.unary_unary = _make_handle_unary_unary(pre_response_callback)
+        elif not self.request_streaming and self.response_streaming:
+            self.unary_stream = _make_handle_unary_stream(pre_response_callback)
+        else:
+            self.stream_unary = _make_handle_stream_unary(pre_response_callback)
 
 
 class _GenericHandler(grpc.GenericRpcHandler):
 
+    def __init__(self, pre_response_callback):
+        self._pre_response_callback = pre_response_callback
+
     def service(self, handler_call_details):
         if handler_call_details.method == _UNARY_UNARY:
-            return _MethodHandler(False, False)
+            return _MethodHandler(False, False, self._pre_response_callback)
+        elif handler_call_details.method == _UNARY_STREAM:
+            return _MethodHandler(False, True, self._pre_response_callback)
+        elif handler_call_details.method == _STREAM_UNARY:
+            return _MethodHandler(True, False, self._pre_response_callback)
         elif handler_call_details.method == _STREAM_STREAM:
-            return _MethodHandler(True, True)
+            return _MethodHandler(True, True, self._pre_response_callback)
         else:
             return None
 
 
+@contextlib.contextmanager
+def _instrumented_client_server_pair(channel_kwargs, server_kwargs,
+                                     server_handler):
+    server = grpc.server(futures.ThreadPoolExecutor(), **server_kwargs)
+    server.add_generic_rpc_handlers((server_handler,))
+    server_port = server.add_insecure_port('{}:0'.format(_HOST))
+    server.start()
+    with _tcp_proxy.TcpProxy(_HOST, _HOST, server_port) as proxy:
+        proxy_port = proxy.get_port()
+        with grpc.insecure_channel('{}:{}'.format(_HOST, proxy_port),
+                                   **channel_kwargs) as client_channel:
+            try:
+                yield client_channel, proxy, server
+            finally:
+                server.stop(None)
+
+
+def _get_byte_counts(channel_kwargs, multicallable_kwargs, client_function,
+                     server_kwargs, server_handler, message):
+    with _instrumented_client_server_pair(channel_kwargs, server_kwargs,
+                                          server_handler) as pipeline:
+        client_channel, proxy, server = pipeline
+        client_function(client_channel, multicallable_kwargs, message)
+        return proxy.get_byte_count()
+
+
+def _get_compression_ratios(client_function, first_channel_kwargs,
+                            first_multicallable_kwargs, first_server_kwargs,
+                            first_server_handler, second_channel_kwargs,
+                            second_multicallable_kwargs, second_server_kwargs,
+                            second_server_handler, message):
+    try:
+        # This test requires the byte length of each connection to be deterministic. As
+        # it turns out, flow control puts bytes on the wire in a nondeterministic
+        # manner. We disable it here in order to measure compression ratios
+        # deterministically.
+        os.environ['GRPC_EXPERIMENTAL_DISABLE_FLOW_CONTROL'] = 'true'
+        first_bytes_sent, first_bytes_received = _get_byte_counts(
+            first_channel_kwargs, first_multicallable_kwargs, client_function,
+            first_server_kwargs, first_server_handler, message)
+        second_bytes_sent, second_bytes_received = _get_byte_counts(
+            second_channel_kwargs, second_multicallable_kwargs, client_function,
+            second_server_kwargs, second_server_handler, message)
+        return ((
+            second_bytes_sent - first_bytes_sent) / float(first_bytes_sent),
+                (second_bytes_received - first_bytes_received) /
+                float(first_bytes_received))
+    finally:
+        del os.environ['GRPC_EXPERIMENTAL_DISABLE_FLOW_CONTROL']
+
+
+def _unary_unary_client(channel, multicallable_kwargs, message):
+    multi_callable = channel.unary_unary(_UNARY_UNARY)
+    response = multi_callable(message, **multicallable_kwargs)
+    if response != message:
+        raise RuntimeError("Request '{}' != Response '{}'".format(
+            message, response))
+
+
+def _unary_stream_client(channel, multicallable_kwargs, message):
+    multi_callable = channel.unary_stream(_UNARY_STREAM)
+    response_iterator = multi_callable(message, **multicallable_kwargs)
+    for response in response_iterator:
+        if response != message:
+            raise RuntimeError("Request '{}' != Response '{}'".format(
+                message, response))
+
+
+def _stream_unary_client(channel, multicallable_kwargs, message):
+    multi_callable = channel.stream_unary(_STREAM_UNARY)
+    requests = (_REQUEST for _ in range(_STREAM_LENGTH))
+    response = multi_callable(requests, **multicallable_kwargs)
+    if response != message:
+        raise RuntimeError("Request '{}' != Response '{}'".format(
+            message, response))
+
+
+def _stream_stream_client(channel, multicallable_kwargs, message):
+    multi_callable = channel.stream_stream(_STREAM_STREAM)
+    request_prefix = str(0).encode('ascii') * 100
+    requests = (
+        request_prefix + str(i).encode('ascii') for i in range(_STREAM_LENGTH))
+    response_iterator = multi_callable(requests, **multicallable_kwargs)
+    for i, response in enumerate(response_iterator):
+        if int(response.decode('ascii')) != i:
+            raise RuntimeError("Request '{}' != Response '{}'".format(
+                i, response))
+
+
 class CompressionTest(unittest.TestCase):
 
-    def setUp(self):
-        self._server = test_common.test_server()
-        self._server.add_generic_rpc_handlers((_GenericHandler(),))
-        self._port = self._server.add_insecure_port('[::]:0')
-        self._server.start()
+    def assertCompressed(self, compression_ratio):
+        self.assertLess(
+            compression_ratio,
+            -1.0 * _COMPRESSION_RATIO_THRESHOLD,
+            msg='Actual compression ratio: {}'.format(compression_ratio))
 
-    def tearDown(self):
-        self._server.stop(None)
+    def assertNotCompressed(self, compression_ratio):
+        self.assertGreaterEqual(
+            compression_ratio,
+            -1.0 * _COMPRESSION_RATIO_THRESHOLD,
+            msg='Actual compession ratio: {}'.format(compression_ratio))
 
-    def testUnary(self):
-        request = b'\x00' * 100
+    def assertConfigurationCompressed(
+            self, client_streaming, server_streaming, channel_compression,
+            multicallable_compression, server_compression,
+            server_call_compression):
+        client_side_compressed = channel_compression or multicallable_compression
+        server_side_compressed = server_compression or server_call_compression
+        channel_kwargs = {
+            'compression': channel_compression,
+        } if channel_compression else {}
+        multicallable_kwargs = {
+            'compression': multicallable_compression,
+        } if multicallable_compression else {}
 
-        # Client -> server compressed through default client channel compression
-        # settings. Server -> client compressed via server-side metadata setting.
-        # TODO(https://github.com/grpc/grpc/issues/4078): replace the "1" integer
-        # literal with proper use of the public API.
-        compressed_channel = grpc.insecure_channel(
-            'localhost:%d' % self._port,
-            options=[('grpc.default_compression_algorithm', 1)])
-        multi_callable = compressed_channel.unary_unary(_UNARY_UNARY)
-        response = multi_callable(request)
-        self.assertEqual(request, response)
+        client_function = None
+        if not client_streaming and not server_streaming:
+            client_function = _unary_unary_client
+        elif not client_streaming and server_streaming:
+            client_function = _unary_stream_client
+        elif client_streaming and not server_streaming:
+            client_function = _stream_unary_client
+        else:
+            client_function = _stream_stream_client
 
-        # Client -> server compressed through client metadata setting. Server ->
-        # client compressed via server-side metadata setting.
-        # TODO(https://github.com/grpc/grpc/issues/4078): replace the "0" integer
-        # literal with proper use of the public API.
-        uncompressed_channel = grpc.insecure_channel(
-            'localhost:%d' % self._port,
-            options=[('grpc.default_compression_algorithm', 0)])
-        multi_callable = compressed_channel.unary_unary(_UNARY_UNARY)
-        response = multi_callable(
-            request, metadata=[('grpc-internal-encoding-request', 'gzip')])
-        self.assertEqual(request, response)
-        compressed_channel.close()
+        server_kwargs = {
+            'compression': server_compression,
+        } if server_compression else {}
+        server_handler = _GenericHandler(
+            functools.partial(set_call_compression, grpc.Compression.Gzip)
+        ) if server_call_compression else _GenericHandler(None)
+        sent_ratio, received_ratio = _get_compression_ratios(
+            client_function, {}, {}, {}, _GenericHandler(None), channel_kwargs,
+            multicallable_kwargs, server_kwargs, server_handler, _REQUEST)
 
-    def testStreaming(self):
-        request = b'\x00' * 100
+        if client_side_compressed:
+            self.assertCompressed(sent_ratio)
+        else:
+            self.assertNotCompressed(sent_ratio)
 
-        # TODO(https://github.com/grpc/grpc/issues/4078): replace the "1" integer
-        # literal with proper use of the public API.
-        compressed_channel = grpc.insecure_channel(
-            'localhost:%d' % self._port,
-            options=[('grpc.default_compression_algorithm', 1)])
-        multi_callable = compressed_channel.stream_stream(_STREAM_STREAM)
-        call = multi_callable(iter([request] * test_constants.STREAM_LENGTH))
-        for response in call:
-            self.assertEqual(request, response)
-        compressed_channel.close()
+        if server_side_compressed:
+            self.assertCompressed(received_ratio)
+        else:
+            self.assertNotCompressed(received_ratio)
 
+    def testDisableNextCompressionStreaming(self):
+        server_kwargs = {
+            'compression': grpc.Compression.Deflate,
+        }
+        _, received_ratio = _get_compression_ratios(
+            _stream_stream_client, {}, {}, {}, _GenericHandler(None), {}, {},
+            server_kwargs, _GenericHandler(disable_next_compression), _REQUEST)
+        self.assertNotCompressed(received_ratio)
+
+    def testDisableNextCompressionStreamingResets(self):
+        server_kwargs = {
+            'compression': grpc.Compression.Deflate,
+        }
+        _, received_ratio = _get_compression_ratios(
+            _stream_stream_client, {}, {}, {}, _GenericHandler(None), {}, {},
+            server_kwargs, _GenericHandler(disable_first_compression), _REQUEST)
+        self.assertCompressed(received_ratio)
+
+
+def _get_compression_str(name, value):
+    return '{}{}'.format(name, _COMPRESSION_NAMES[value])
+
+
+def _get_compression_test_name(client_streaming, server_streaming,
+                               channel_compression, multicallable_compression,
+                               server_compression, server_call_compression):
+    client_arity = 'Stream' if client_streaming else 'Unary'
+    server_arity = 'Stream' if server_streaming else 'Unary'
+    arity = '{}{}'.format(client_arity, server_arity)
+    channel_compression_str = _get_compression_str('Channel',
+                                                   channel_compression)
+    multicallable_compression_str = _get_compression_str(
+        'Multicallable', multicallable_compression)
+    server_compression_str = _get_compression_str('Server', server_compression)
+    server_call_compression_str = _get_compression_str('ServerCall',
+                                                       server_call_compression)
+    return 'test{}{}{}{}{}'.format(
+        arity, channel_compression_str, multicallable_compression_str,
+        server_compression_str, server_call_compression_str)
+
+
+def _test_options():
+    for test_parameters in itertools.product(*_TEST_OPTIONS.values()):
+        yield dict(zip(_TEST_OPTIONS.keys(), test_parameters))
+
+
+for options in _test_options():
+
+    def test_compression(**kwargs):
+
+        def _test_compression(self):
+            self.assertConfigurationCompressed(**kwargs)
+
+        return _test_compression
+
+    setattr(CompressionTest, _get_compression_test_name(**options),
+            test_compression(**options))
 
 if __name__ == '__main__':
     logging.basicConfig()
diff --git a/src/python/grpcio_tests/tests/unit/_tcp_proxy.py b/src/python/grpcio_tests/tests/unit/_tcp_proxy.py
new file mode 100644
index 0000000..5ad0bf8
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/_tcp_proxy.py
@@ -0,0 +1,164 @@
+# Copyright 2019 the 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.
+""" Proxies a TCP connection between a single client-server pair.
+
+This proxy is not suitable for production, but should work well for cases in
+which a test needs to spy on the bytes put on the wire between a server and
+a client.
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import datetime
+import select
+import socket
+import threading
+
+_TCP_PROXY_BUFFER_SIZE = 1024
+_TCP_PROXY_TIMEOUT = datetime.timedelta(milliseconds=500)
+
+
+def _create_socket_ipv6(bind_address):
+    listen_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+    listen_socket.bind((bind_address, 0, 0, 0))
+    return listen_socket
+
+
+def _create_socket_ipv4(bind_address):
+    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    listen_socket.bind((bind_address, 0))
+    return listen_socket
+
+
+def _init_listen_socket(bind_address):
+    listen_socket = None
+    if socket.has_ipv6:
+        try:
+            listen_socket = _create_socket_ipv6(bind_address)
+        except socket.error:
+            listen_socket = _create_socket_ipv4(bind_address)
+    else:
+        listen_socket = _create_socket_ipv4(bind_address)
+    listen_socket.listen(1)
+    return listen_socket, listen_socket.getsockname()[1]
+
+
+def _init_proxy_socket(gateway_address, gateway_port):
+    proxy_socket = socket.create_connection((gateway_address, gateway_port))
+    return proxy_socket
+
+
+class TcpProxy(object):
+    """Proxies a TCP connection between one client and one server."""
+
+    def __init__(self, bind_address, gateway_address, gateway_port):
+        self._bind_address = bind_address
+        self._gateway_address = gateway_address
+        self._gateway_port = gateway_port
+
+        self._byte_count_lock = threading.RLock()
+        self._sent_byte_count = 0
+        self._received_byte_count = 0
+
+        self._stop_event = threading.Event()
+
+        self._port = None
+        self._listen_socket = None
+        self._proxy_socket = None
+
+        # The following three attributes are owned by the serving thread.
+        self._northbound_data = b""
+        self._southbound_data = b""
+        self._client_sockets = []
+
+        self._thread = threading.Thread(target=self._run_proxy)
+
+    def start(self):
+        self._listen_socket, self._port = _init_listen_socket(
+            self._bind_address)
+        self._proxy_socket = _init_proxy_socket(self._gateway_address,
+                                                self._gateway_port)
+        self._thread.start()
+
+    def get_port(self):
+        return self._port
+
+    def _handle_reads(self, sockets_to_read):
+        for socket_to_read in sockets_to_read:
+            if socket_to_read is self._listen_socket:
+                client_socket, client_address = socket_to_read.accept()
+                self._client_sockets.append(client_socket)
+            elif socket_to_read is self._proxy_socket:
+                data = socket_to_read.recv(_TCP_PROXY_BUFFER_SIZE)
+                with self._byte_count_lock:
+                    self._received_byte_count += len(data)
+                self._northbound_data += data
+            elif socket_to_read in self._client_sockets:
+                data = socket_to_read.recv(_TCP_PROXY_BUFFER_SIZE)
+                if data:
+                    with self._byte_count_lock:
+                        self._sent_byte_count += len(data)
+                    self._southbound_data += data
+                else:
+                    self._client_sockets.remove(socket_to_read)
+            else:
+                raise RuntimeError('Unidentified socket appeared in read set.')
+
+    def _handle_writes(self, sockets_to_write):
+        for socket_to_write in sockets_to_write:
+            if socket_to_write is self._proxy_socket:
+                if self._southbound_data:
+                    self._proxy_socket.sendall(self._southbound_data)
+                    self._southbound_data = b""
+            elif socket_to_write in self._client_sockets:
+                if self._northbound_data:
+                    socket_to_write.sendall(self._northbound_data)
+                    self._northbound_data = b""
+
+    def _run_proxy(self):
+        while not self._stop_event.is_set():
+            expected_reads = (self._listen_socket, self._proxy_socket) + tuple(
+                self._client_sockets)
+            expected_writes = expected_reads
+            sockets_to_read, sockets_to_write, _ = select.select(
+                expected_reads, expected_writes, (),
+                _TCP_PROXY_TIMEOUT.total_seconds())
+            self._handle_reads(sockets_to_read)
+            self._handle_writes(sockets_to_write)
+        for client_socket in self._client_sockets:
+            client_socket.close()
+
+    def stop(self):
+        self._stop_event.set()
+        self._thread.join()
+        self._listen_socket.close()
+        self._proxy_socket.close()
+
+    def get_byte_count(self):
+        with self._byte_count_lock:
+            return self._sent_byte_count, self._received_byte_count
+
+    def reset_byte_count(self):
+        with self._byte_count_lock:
+            self._byte_count = 0
+            self._received_byte_count = 0
+
+    def __enter__(self):
+        self.start()
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.stop()
diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c
index 4916cee..4aa4121 100644
--- a/src/ruby/ext/grpc/rb_grpc.c
+++ b/src/ruby/ext/grpc/rb_grpc.c
@@ -304,7 +304,7 @@
 static int bg_thread_init_done = 0;
 
 static void grpc_ruby_init_threads() {
-  // Avoid calling calling into ruby library (when creating threads here)
+  // Avoid calling into ruby library (when creating threads here)
   // in gpr_once_init. In general, it appears to be unsafe to call
   // into the ruby library while holding a non-ruby mutex, because a gil yield
   // could end up trying to lock onto that same mutex and deadlocking.
diff --git a/src/ruby/lib/grpc/errors.rb b/src/ruby/lib/grpc/errors.rb
index 0c15c2f..0b27bee 100644
--- a/src/ruby/lib/grpc/errors.rb
+++ b/src/ruby/lib/grpc/errors.rb
@@ -42,12 +42,31 @@
       @metadata = metadata
     end
 
-    # Converts the exception to a GRPC::Status for use in the networking
+    # Converts the exception to a {Struct::Status} for use in the networking
     # wrapper layer.
     #
-    # @return [Status] with the same code and details
+    # @return [Struct::Status] with the same code and details
     def to_status
-      Struct::Status.new(code, details, @metadata)
+      Struct::Status.new(code, details, metadata)
+    end
+
+    # Converts the exception to a deserialized {Google::Rpc::Status} object.
+    # Returns `nil` if the `grpc-status-details-bin` trailer could not be
+    # converted to a {Google::Rpc::Status} due to the server not providing
+    # the necessary trailers.
+    #
+    # @return [Google::Rpc::Status, nil]
+    def to_rpc_status
+      # Lazily require google_rpc_status_utils to scope
+      # loading protobuf_c.so to the users of this method.
+      require_relative './google_rpc_status_utils'
+      status = to_status
+      return if status.nil?
+      GoogleRpcStatusUtils.extract_google_rpc_status(status)
+    rescue Google::Protobuf::ParseError => parse_error
+      GRPC.logger.warn('parse error: to_rpc_status failed')
+      GRPC.logger.warn(parse_error)
+      nil
     end
 
     def self.new_status_exception(code, details = 'unknown cause',
diff --git a/src/ruby/lib/grpc/generic/bidi_call.rb b/src/ruby/lib/grpc/generic/bidi_call.rb
index ffb232b..a4d4af6 100644
--- a/src/ruby/lib/grpc/generic/bidi_call.rb
+++ b/src/ruby/lib/grpc/generic/bidi_call.rb
@@ -224,7 +224,7 @@
         set_input_stream_done.call
       end
       GRPC.logger.debug('bidi-read-loop: finished')
-      # Make sure that the write loop is done done before finishing the call.
+      # Make sure that the write loop is done before finishing the call.
       # Note that blocking is ok at this point because we've already received
       # a status
       @enq_th.join if is_client
diff --git a/src/ruby/lib/grpc/generic/rpc_server.rb b/src/ruby/lib/grpc/generic/rpc_server.rb
index dfeb47f..3d34419 100644
--- a/src/ruby/lib/grpc/generic/rpc_server.rb
+++ b/src/ruby/lib/grpc/generic/rpc_server.rb
@@ -202,7 +202,7 @@
     # forcing an abrupt exit to each thread.
     #
     # * connect_md_proc:
-    # when non-nil is a proc for determining metadata to to send back the client
+    # when non-nil is a proc for determining metadata to send back the client
     # on receiving an invocation req.  The proc signature is:
     #   {key: val, ..} func(method_name, {key: val, ...})
     #
diff --git a/src/ruby/lib/grpc/version.rb b/src/ruby/lib/grpc/version.rb
index 620f67f..cca795b 100644
--- a/src/ruby/lib/grpc/version.rb
+++ b/src/ruby/lib/grpc/version.rb
@@ -14,5 +14,5 @@
 
 # GRPC contains the General RPC module.
 module GRPC
-  VERSION = '1.20.1'
+  VERSION = '1.21.0.dev'
 end
diff --git a/src/ruby/spec/errors_spec.rb b/src/ruby/spec/errors_spec.rb
new file mode 100644
index 0000000..bf27a53
--- /dev/null
+++ b/src/ruby/spec/errors_spec.rb
@@ -0,0 +1,141 @@
+# 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.
+
+require 'spec_helper'
+require 'google/protobuf/well_known_types'
+require_relative '../pb/src/proto/grpc/testing/messages_pb'
+
+describe GRPC::BadStatus do
+  describe :attributes do
+    it 'has attributes' do
+      code = 1
+      details = 'details'
+      metadata = { 'key' => 'val' }
+
+      exception = GRPC::BadStatus.new(code, details, metadata)
+
+      expect(exception.code).to eq code
+      expect(exception.details).to eq details
+      expect(exception.metadata).to eq metadata
+    end
+  end
+
+  describe :new_status_exception do
+    let(:codes_and_classes) do
+      [
+        [GRPC::Core::StatusCodes::OK, GRPC::Ok],
+        [GRPC::Core::StatusCodes::CANCELLED, GRPC::Cancelled],
+        [GRPC::Core::StatusCodes::UNKNOWN, GRPC::Unknown],
+        [GRPC::Core::StatusCodes::INVALID_ARGUMENT, GRPC::InvalidArgument],
+        [GRPC::Core::StatusCodes::DEADLINE_EXCEEDED, GRPC::DeadlineExceeded],
+        [GRPC::Core::StatusCodes::NOT_FOUND, GRPC::NotFound],
+        [GRPC::Core::StatusCodes::ALREADY_EXISTS, GRPC::AlreadyExists],
+        [GRPC::Core::StatusCodes::PERMISSION_DENIED, GRPC::PermissionDenied],
+        [GRPC::Core::StatusCodes::UNAUTHENTICATED, GRPC::Unauthenticated],
+        [GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED, GRPC::ResourceExhausted],
+        [GRPC::Core::StatusCodes::FAILED_PRECONDITION, GRPC::FailedPrecondition],
+        [GRPC::Core::StatusCodes::ABORTED, GRPC::Aborted],
+        [GRPC::Core::StatusCodes::OUT_OF_RANGE, GRPC::OutOfRange],
+        [GRPC::Core::StatusCodes::UNIMPLEMENTED, GRPC::Unimplemented],
+        [GRPC::Core::StatusCodes::INTERNAL, GRPC::Internal],
+        [GRPC::Core::StatusCodes::UNAVAILABLE, GRPC::Unavailable],
+        [GRPC::Core::StatusCodes::DATA_LOSS, GRPC::DataLoss],
+        [99, GRPC::BadStatus] # Unknown codes default to BadStatus
+      ]
+    end
+
+    it 'maps codes to the correct error class' do
+      codes_and_classes.each do |code, grpc_error_class|
+        exception = GRPC::BadStatus.new_status_exception(code)
+
+        expect(exception).to be_a grpc_error_class
+      end
+    end
+  end
+
+  describe :to_status do
+    it 'gets status' do
+      code = 1
+      details = 'details'
+      metadata = { 'key' => 'val' }
+
+      exception = GRPC::BadStatus.new(code, details, metadata)
+      status = Struct::Status.new(code, details, metadata)
+
+      expect(exception.to_status).to eq status
+    end
+  end
+
+  describe :to_rpc_status do
+    let(:simple_request_any) do
+      Google::Protobuf::Any.new.tap do |any|
+        any.pack(
+          Grpc::Testing::SimpleRequest.new(
+            payload: Grpc::Testing::Payload.new(body: 'request')
+          )
+        )
+      end
+    end
+    let(:simple_response_any) do
+      Google::Protobuf::Any.new.tap do |any|
+        any.pack(
+          Grpc::Testing::SimpleResponse.new(
+            payload: Grpc::Testing::Payload.new(body: 'response')
+          )
+        )
+      end
+    end
+    let(:payload_any) do
+      Google::Protobuf::Any.new.tap do |any|
+        any.pack(Grpc::Testing::Payload.new(body: 'payload'))
+      end
+    end
+
+    it 'decodes proto values' do
+      rpc_status = Google::Rpc::Status.new(
+        code: 1,
+        message: 'matching message',
+        details: [simple_request_any, simple_response_any, payload_any]
+      )
+      rpc_status_proto = Google::Rpc::Status.encode(rpc_status)
+
+      code = 1
+      details = 'details'
+      metadata = { 'grpc-status-details-bin' => rpc_status_proto }
+
+      exception = GRPC::BadStatus.new(code, details, metadata)
+
+      expect(exception.to_rpc_status).to eq rpc_status
+    end
+
+    it 'does not raise when decoding a bad proto' do
+      code = 1
+      details = 'details'
+      metadata = { 'grpc-status-details-bin' => 'notavalidprotostream' }
+
+      exception = GRPC::BadStatus.new(code, details, metadata)
+
+      expect(exception.to_rpc_status).to be nil
+
+      error_msg = 'parse error: to_rpc_status failed'
+      error_desc = '<Google::Protobuf::ParseError> ' \
+        'Error occurred during parsing: Invalid wire type'
+
+      # Check that the parse error was logged correctly
+      log_contents = @log_output.read
+      expect(log_contents).to include "WARN  GRPC : #{error_msg}"
+      expect(log_contents).to include "WARN  GRPC : #{error_desc}"
+    end
+  end
+end
diff --git a/src/ruby/tools/version.rb b/src/ruby/tools/version.rb
index 5860fbc..5604e21 100644
--- a/src/ruby/tools/version.rb
+++ b/src/ruby/tools/version.rb
@@ -14,6 +14,6 @@
 
 module GRPC
   module Tools
-    VERSION = '1.20.1'
+    VERSION = '1.21.0.dev'
   end
 end
diff --git a/templates/Makefile.template b/templates/Makefile.template
index 71391f8..24bea5c 100644
--- a/templates/Makefile.template
+++ b/templates/Makefile.template
@@ -345,11 +345,11 @@
   ${arg} += $(EXTRA_${arg})
   % endfor
 
-  HOST_CPPFLAGS = $(CPPFLAGS)
-  HOST_CFLAGS = $(CFLAGS)
-  HOST_CXXFLAGS = $(CXXFLAGS)
-  HOST_LDFLAGS = $(LDFLAGS)
-  HOST_LDLIBS = $(LDLIBS)
+  HOST_CPPFLAGS += $(CPPFLAGS)
+  HOST_CFLAGS += $(CFLAGS)
+  HOST_CXXFLAGS += $(CXXFLAGS)
+  HOST_LDFLAGS += $(LDFLAGS)
+  HOST_LDLIBS += $(LDLIBS)
 
   # These are automatically computed variables.
   # There shouldn't be any need to change anything from now on.
diff --git a/templates/gRPC-Core.podspec.template b/templates/gRPC-Core.podspec.template
index 93dc735..89a1e91 100644
--- a/templates/gRPC-Core.podspec.template
+++ b/templates/gRPC-Core.podspec.template
@@ -70,14 +70,6 @@
     excl = grpc_private_files(libs)
     return [file for file in out if not file in excl]
 
-  def cfstream_private_headers(libs):
-    out = grpc_lib_files(libs, ("grpc_cfstream",), ("own_headers",))
-    return out
-
-  def cfstream_private_files(libs):
-    out = grpc_lib_files(libs, ("grpc_cfstream",), ("own_src", "own_headers"))
-    return out
-
   def ruby_multiline_list(files, indent):
     return (',\n' + indent*' ').join('\'%s\'' % f for f in files)
   %>
@@ -188,14 +180,9 @@
       ss.private_header_files = ${ruby_multiline_list(grpc_private_headers(libs), 30)}
     end
 
+    # CFStream is now default. Leaving this subspec only for compatibility purpose.
     s.subspec 'CFStream-Implementation' do |ss|
-      ss.header_mappings_dir = '.'
       ss.dependency "#{s.name}/Implementation", version
-      ss.pod_target_xcconfig = {
-        'GCC_PREPROCESSOR_DEFINITIONS' => 'GRPC_CFSTREAM=1'
-      }
-      ss.source_files = ${ruby_multiline_list(cfstream_private_files(filegroups), 22)}
-      ss.private_header_files = ${ruby_multiline_list(cfstream_private_headers(filegroups), 30)}
     end
 
     s.subspec 'Cronet-Interface' do |ss|
diff --git a/templates/gRPC-ProtoRPC.podspec.template b/templates/gRPC-ProtoRPC.podspec.template
index 9d7e392..e4d5f3f 100644
--- a/templates/gRPC-ProtoRPC.podspec.template
+++ b/templates/gRPC-ProtoRPC.podspec.template
@@ -55,12 +55,10 @@
 
       ss.source_files = "#{src_dir}/*.{h,m}"
     end
+
+    # CFStream is now default. Leaving this subspec only for compatibility purpose.
     s.subspec 'CFStream' do |ss|
-      ss.dependency 'gRPC/CFStream', version
       ss.dependency "#{s.name}/Main", version
-      ss.pod_target_xcconfig = {
-        'GCC_PREPROCESSOR_DEFINITIONS' => 'GRPC_CFSTREAM=1'
-      }
     end
 
     s.pod_target_xcconfig = {
diff --git a/templates/gRPC.podspec.template b/templates/gRPC.podspec.template
index 6fe20e6..c972393 100644
--- a/templates/gRPC.podspec.template
+++ b/templates/gRPC.podspec.template
@@ -66,14 +66,9 @@
       ss.dependency 'gRPC-Core', version
     end
 
-    # This subspec is mutually exclusive with the `Main` subspec
+    # CFStream is now default. Leaving this subspec only for compatibility purpose.
     s.subspec 'CFStream' do |ss|
-      ss.dependency 'gRPC-Core/CFStream-Implementation', version
       ss.dependency "#{s.name}/Main", version
-
-      ss.pod_target_xcconfig = {
-        'GCC_PREPROCESSOR_DEFINITIONS' => 'GRPC_CFSTREAM=1'
-      }
     end
 
     s.subspec 'GID' do |ss|
diff --git a/templates/grpc.gemspec.template b/templates/grpc.gemspec.template
index 0e32171..d2b54a9 100644
--- a/templates/grpc.gemspec.template
+++ b/templates/grpc.gemspec.template
@@ -32,7 +32,7 @@
     s.platform      = Gem::Platform::RUBY
 
     s.add_dependency 'google-protobuf', '~> 3.7'
-    s.add_dependency 'googleapis-common-protos-types', '~> 1.0.0'
+    s.add_dependency 'googleapis-common-protos-types', '~> 1.0'
 
     s.add_development_dependency 'bundler',            '~> 1.9'
     s.add_development_dependency 'facter',             '~> 2.4'
diff --git a/templates/src/csharp/Grpc.Core/Internal/native_methods.include b/templates/src/csharp/Grpc.Core/Internal/native_methods.include
index b7a8e28..4a31171 100644
--- a/templates/src/csharp/Grpc.Core/Internal/native_methods.include
+++ b/templates/src/csharp/Grpc.Core/Internal/native_methods.include
@@ -6,7 +6,7 @@
     'BatchContextSafeHandle grpcsharp_batch_context_create()',
     'IntPtr grpcsharp_batch_context_recv_initial_metadata(BatchContextSafeHandle ctx)',
     'IntPtr grpcsharp_batch_context_recv_message_length(BatchContextSafeHandle ctx)',
-    'void grpcsharp_batch_context_recv_message_to_buffer(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen)',
+    'int grpcsharp_batch_context_recv_message_next_slice_peek(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr)',
     'StatusCode grpcsharp_batch_context_recv_status_on_client_status(BatchContextSafeHandle ctx)',
     'IntPtr grpcsharp_batch_context_recv_status_on_client_details(BatchContextSafeHandle ctx, out UIntPtr detailsLength)',
     'IntPtr grpcsharp_batch_context_recv_status_on_client_trailing_metadata(BatchContextSafeHandle ctx)',
@@ -44,7 +44,7 @@
     'void grpcsharp_channel_args_set_integer(ChannelArgsSafeHandle args, UIntPtr index, string key, int value)',
     'void grpcsharp_channel_args_destroy(IntPtr args)',
     'void grpcsharp_override_default_ssl_roots(string pemRootCerts)',
-    'ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey)',
+    'ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, IntPtr verifyPeerCallbackTag)',
     'ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds)',
     'void grpcsharp_channel_credentials_release(IntPtr credentials)',
     'ChannelSafeHandle grpcsharp_insecure_channel_create(string target, ChannelArgsSafeHandle channelArgs)',
diff --git a/templates/tools/dockerfile/bazel.include b/templates/tools/dockerfile/bazel.include
index 62d6860..8888e93 100644
--- a/templates/tools/dockerfile/bazel.include
+++ b/templates/tools/dockerfile/bazel.include
@@ -2,6 +2,6 @@
 # Bazel installation
 
 RUN apt-get update && apt-get install -y wget && apt-get clean
-RUN wget https://github.com/bazelbuild/bazel/releases/download/0.20.0/bazel-0.20.0-installer-linux-x86_64.sh && ${'\\'}
-  bash ./bazel-0.20.0-installer-linux-x86_64.sh && ${'\\'}
-  rm bazel-0.20.0-installer-linux-x86_64.sh
+RUN wget https://github.com/bazelbuild/bazel/releases/download/0.23.2/bazel-0.23.2-installer-linux-x86_64.sh && ${'\\'}
+  bash ./bazel-0.23.2-installer-linux-x86_64.sh && ${'\\'}
+  rm bazel-0.23.2-installer-linux-x86_64.sh
diff --git a/templates/tools/dockerfile/debian_jessie_header.include b/templates/tools/dockerfile/debian_jessie_header.include
index 11bd5f9..f71f986 100644
--- a/templates/tools/dockerfile/debian_jessie_header.include
+++ b/templates/tools/dockerfile/debian_jessie_header.include
@@ -1,2 +1 @@
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
diff --git a/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile.include b/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile.include
index e80b517..7307e29 100644
--- a/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile.include
+++ b/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile.include
@@ -15,13 +15,6 @@
 <%include file="../../debian_jessie_header.include"/>
 
 <%include file="java_deps.include"/>
-<%include file="../../python_deps.include"/>
-
-# Trigger download of as many Gradle artifacts as possible.
-RUN git clone --recursive --depth 1 https://github.com/grpc/grpc-java.git && ${'\\'}
-  cd grpc-java && ${'\\'}
-  ./gradlew :grpc-interop-testing:installDist -PskipCodegen=true && ${'\\'}
-  rm -r "$(pwd)"
 
 # Define the default command.
-CMD ["bash"]
\ No newline at end of file
+CMD ["bash"]
diff --git a/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/java_deps.include b/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/java_deps.include
index 40d70e0..c05b564 100644
--- a/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/java_deps.include
+++ b/templates/tools/dockerfile/interoptest/grpc_interop_java_oracle8/java_deps.include
@@ -1,16 +1,11 @@
-# Install JDK 8 and Git
+# Install JDK 8
 #
 RUN echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections && ${'\\'}
   echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee /etc/apt/sources.list.d/webupd8team-java.list && ${'\\'}
   echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list && ${'\\'}
-  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
-
-RUN apt-get update && apt-get -y install ${'\\'}
-      git ${'\\'}
-      libapr1 ${'\\'}
-      oracle-java8-installer ${'\\'}
-      && ${'\\'}
-    apt-get clean && rm -r /var/cache/oracle-jdk8-installer/
+  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886 && ${'\\'}
+  apt-get update && apt-get -y install oracle-java8-installer && ${'\\'}
+  apt-get clean && rm -r /var/cache/oracle-jdk8-installer/
 
 ENV JAVA_HOME /usr/lib/jvm/java-8-oracle
 ENV PATH $PATH:$JAVA_HOME/bin
diff --git a/templates/tools/dockerfile/java_build_interop.sh.include b/templates/tools/dockerfile/java_build_interop.sh.include
index 16d5fb6..e30b53e 100755
--- a/templates/tools/dockerfile/java_build_interop.sh.include
+++ b/templates/tools/dockerfile/java_build_interop.sh.include
@@ -16,16 +16,24 @@
 # Builds Java interop server and client in a base image.
 set -e
 
-mkdir -p /var/local/git
-git clone --recursive --depth 1 /var/local/jenkins/grpc-java /var/local/git/grpc-java
+cp -r /var/local/jenkins/grpc-java /tmp/grpc-java
 
 # copy service account keys if available
 cp -r /var/local/jenkins/service_account $HOME || true
 
-cd /var/local/git/grpc-java
-
+pushd /tmp/grpc-java
 ./gradlew :grpc-interop-testing:installDist -PskipCodegen=true
 
+mkdir -p /var/local/git/grpc-java/
+cp -r --parents -t /var/local/git/grpc-java/ ${'\\'}
+    interop-testing/build/install/ ${'\\'}
+    run-test-client.sh ${'\\'}
+    run-test-server.sh
+
+popd
+rm -r /tmp/grpc-java
+rm -r "$HOME/.gradle"
+
 # enable extra java logging
 mkdir -p /var/local/grpc_java_logging
 echo "handlers = java.util.logging.ConsoleHandler
diff --git a/test/core/bad_client/bad_client.cc b/test/core/bad_client/bad_client.cc
index 6b49252..26550a2 100644
--- a/test/core/bad_client/bad_client.cc
+++ b/test/core/bad_client/bad_client.cc
@@ -257,7 +257,7 @@
     return false;
   }
   grpc_slice slice = incoming->slices[0];
-  /* There should be atleast a settings frame present */
+  /* There should be at least one settings frame present */
   if (GRPC_SLICE_LENGTH(slice) < MIN_HTTP2_FRAME_SIZE) {
     return false;
   }
diff --git a/test/core/bad_connection/close_fd_test.cc b/test/core/bad_connection/close_fd_test.cc
index 317526a..78a1a5c 100644
--- a/test/core/bad_connection/close_fd_test.cc
+++ b/test/core/bad_connection/close_fd_test.cc
@@ -39,7 +39,6 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/iomgr/endpoint_pair.h"
 #include "src/core/lib/surface/channel.h"
 #include "src/core/lib/surface/completion_queue.h"
@@ -328,7 +327,6 @@
    */
   if (event.type == GRPC_QUEUE_TIMEOUT) {
     GPR_ASSERT(event.success == 0);
-    GPR_ASSERT(event.tag == nullptr);
     /* status is not initialized */
     GPR_ASSERT(status == GRPC_STATUS__DO_NOT_USE);
   } else {
@@ -531,7 +529,6 @@
   } else {
     GPR_ASSERT(event.type == GRPC_QUEUE_TIMEOUT);
     GPR_ASSERT(event.success == 0);
-    GPR_ASSERT(event.tag == nullptr);
     /* status is not initialized */
     GPR_ASSERT(status == GRPC_STATUS__DO_NOT_USE);
   }
@@ -664,7 +661,6 @@
       g_ctx.cq, grpc_timeout_milliseconds_to_deadline(100), nullptr);
   GPR_ASSERT(event.success == 0);
   GPR_ASSERT(event.type == GRPC_QUEUE_TIMEOUT);
-  GPR_ASSERT(event.tag == nullptr);
 
   grpc_slice_unref(details);
   grpc_metadata_array_destroy(&initial_metadata_recv);
@@ -720,13 +716,11 @@
       g_ctx.client_cq, grpc_timeout_milliseconds_to_deadline(100), nullptr);
   GPR_ASSERT(event.type == GRPC_QUEUE_TIMEOUT);
   GPR_ASSERT(event.success == 0);
-  GPR_ASSERT(event.tag == nullptr);
 
   event = grpc_completion_queue_next(
       g_ctx.cq, grpc_timeout_milliseconds_to_deadline(100), nullptr);
   GPR_ASSERT(event.type == GRPC_QUEUE_TIMEOUT);
   GPR_ASSERT(event.success == 0);
-  GPR_ASSERT(event.tag == nullptr);
 
   grpc_call_unref(call);
   end_test();
diff --git a/test/core/bad_ssl/bad_ssl_test.cc b/test/core/bad_ssl/bad_ssl_test.cc
index 73d251e..8dd55f6 100644
--- a/test/core/bad_ssl/bad_ssl_test.cc
+++ b/test/core/bad_ssl/bad_ssl_test.cc
@@ -25,9 +25,9 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/util/port.h"
 #include "test/core/util/subprocess.h"
@@ -133,7 +133,7 @@
     strcpy(root, ".");
   }
   if (argc == 2) {
-    gpr_setenv("GRPC_DEFAULT_SSL_ROOTS_FILE_PATH", argv[1]);
+    GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, argv[1]);
   }
   /* figure out our test name */
   tmp = lunder - 1;
diff --git a/test/core/channel/channel_args_test.cc b/test/core/channel/channel_args_test.cc
index 087a767..9c47d5d 100644
--- a/test/core/channel/channel_args_test.cc
+++ b/test/core/channel/channel_args_test.cc
@@ -58,96 +58,6 @@
   grpc_channel_args_destroy(ch_args);
 }
 
-static void test_set_compression_algorithm(void) {
-  grpc_core::ExecCtx exec_ctx;
-  grpc_channel_args* ch_args;
-
-  ch_args =
-      grpc_channel_args_set_compression_algorithm(nullptr, GRPC_COMPRESS_GZIP);
-  GPR_ASSERT(ch_args->num_args == 1);
-  GPR_ASSERT(strcmp(ch_args->args[0].key,
-                    GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM) == 0);
-  GPR_ASSERT(ch_args->args[0].type == GRPC_ARG_INTEGER);
-
-  grpc_channel_args_destroy(ch_args);
-}
-
-static void test_compression_algorithm_states(void) {
-  grpc_core::ExecCtx exec_ctx;
-  grpc_channel_args *ch_args, *ch_args_wo_gzip, *ch_args_wo_gzip_deflate,
-      *ch_args_wo_gzip_deflate_gzip;
-  unsigned states_bitset;
-  size_t i;
-
-  ch_args = grpc_channel_args_copy_and_add(nullptr, nullptr, 0);
-  /* by default, all enabled */
-  states_bitset = static_cast<unsigned>(
-      grpc_channel_args_compression_algorithm_get_states(ch_args));
-
-  for (i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
-    GPR_ASSERT(GPR_BITGET(states_bitset, i));
-  }
-
-  /* disable gzip and deflate and stream/gzip */
-  ch_args_wo_gzip = grpc_channel_args_compression_algorithm_set_state(
-      &ch_args, GRPC_COMPRESS_GZIP, 0);
-  GPR_ASSERT(ch_args == ch_args_wo_gzip);
-  ch_args_wo_gzip_deflate = grpc_channel_args_compression_algorithm_set_state(
-      &ch_args_wo_gzip, GRPC_COMPRESS_DEFLATE, 0);
-  GPR_ASSERT(ch_args_wo_gzip == ch_args_wo_gzip_deflate);
-  ch_args_wo_gzip_deflate_gzip =
-      grpc_channel_args_compression_algorithm_set_state(
-          &ch_args_wo_gzip_deflate, GRPC_COMPRESS_STREAM_GZIP, 0);
-  GPR_ASSERT(ch_args_wo_gzip_deflate == ch_args_wo_gzip_deflate_gzip);
-
-  states_bitset =
-      static_cast<unsigned>(grpc_channel_args_compression_algorithm_get_states(
-          ch_args_wo_gzip_deflate));
-  for (i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
-    if (i == GRPC_COMPRESS_GZIP || i == GRPC_COMPRESS_DEFLATE ||
-        i == GRPC_COMPRESS_STREAM_GZIP) {
-      GPR_ASSERT(GPR_BITGET(states_bitset, i) == 0);
-    } else {
-      GPR_ASSERT(GPR_BITGET(states_bitset, i) != 0);
-    }
-  }
-
-  /* re-enabled gzip and stream/gzip only */
-  ch_args_wo_gzip = grpc_channel_args_compression_algorithm_set_state(
-      &ch_args_wo_gzip_deflate_gzip, GRPC_COMPRESS_GZIP, 1);
-  ch_args_wo_gzip = grpc_channel_args_compression_algorithm_set_state(
-      &ch_args_wo_gzip, GRPC_COMPRESS_STREAM_GZIP, 1);
-  GPR_ASSERT(ch_args_wo_gzip == ch_args_wo_gzip_deflate_gzip);
-
-  states_bitset = static_cast<unsigned>(
-      grpc_channel_args_compression_algorithm_get_states(ch_args_wo_gzip));
-  for (i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
-    if (i == GRPC_COMPRESS_DEFLATE) {
-      GPR_ASSERT(GPR_BITGET(states_bitset, i) == 0);
-    } else {
-      GPR_ASSERT(GPR_BITGET(states_bitset, i) != 0);
-    }
-  }
-
-  grpc_channel_args_destroy(ch_args);
-}
-
-static void test_set_socket_mutator(void) {
-  grpc_channel_args* ch_args;
-  grpc_socket_mutator mutator;
-  grpc_socket_mutator_init(&mutator, nullptr);
-
-  ch_args = grpc_channel_args_set_socket_mutator(nullptr, &mutator);
-  GPR_ASSERT(ch_args->num_args == 1);
-  GPR_ASSERT(strcmp(ch_args->args[0].key, GRPC_ARG_SOCKET_MUTATOR) == 0);
-  GPR_ASSERT(ch_args->args[0].type == GRPC_ARG_POINTER);
-
-  {
-    grpc_core::ExecCtx exec_ctx;
-    grpc_channel_args_destroy(ch_args);
-  }
-}
-
 struct fake_class {
   int foo;
 };
@@ -234,9 +144,6 @@
   grpc::testing::TestEnvironment env(argc, argv);
   grpc_init();
   test_create();
-  test_set_compression_algorithm();
-  test_compression_algorithm_states();
-  test_set_socket_mutator();
   test_channel_create_with_args();
   test_server_create_with_args();
   grpc_shutdown();
diff --git a/test/core/client_channel/BUILD b/test/core/client_channel/BUILD
index 68a7163..a9cfa96 100644
--- a/test/core/client_channel/BUILD
+++ b/test/core/client_channel/BUILD
@@ -79,3 +79,17 @@
         "//test/core/util:grpc_test_util",
     ],
 )
+
+grpc_cc_test(
+    name = "service_config_test",
+    srcs = ["service_config_test.cc"],
+    external_deps = [
+        "gtest",
+    ],
+    language = "C++",
+    deps = [
+        "//:gpr",
+        "//:grpc",
+        "//test/core/util:grpc_test_util",
+    ],
+)
diff --git a/test/core/client_channel/resolvers/dns_resolver_test.cc b/test/core/client_channel/resolvers/dns_resolver_test.cc
index ed3b4e6..129866b 100644
--- a/test/core/client_channel/resolvers/dns_resolver_test.cc
+++ b/test/core/client_channel/resolvers/dns_resolver_test.cc
@@ -21,8 +21,8 @@
 #include <grpc/support/log.h>
 
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "test/core/util/test_config.h"
@@ -78,13 +78,13 @@
   test_succeeds(dns, "dns:10.2.1.1:1234");
   test_succeeds(dns, "dns:www.google.com");
   test_succeeds(dns, "dns:///www.google.com");
-  char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (resolver_env != nullptr && gpr_stricmp(resolver_env, "native") == 0) {
+  grpc_core::UniquePtr<char> resolver =
+      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+  if (gpr_stricmp(resolver.get(), "native") == 0) {
     test_fails(dns, "dns://8.8.8.8/8.8.8.8:8888");
   } else {
     test_succeeds(dns, "dns://8.8.8.8/8.8.8.8:8888");
   }
-  gpr_free(resolver_env);
   {
     grpc_core::ExecCtx exec_ctx;
     GRPC_COMBINER_UNREF(g_combiner, "test");
diff --git a/test/core/client_channel/service_config_test.cc b/test/core/client_channel/service_config_test.cc
new file mode 100644
index 0000000..9734304
--- /dev/null
+++ b/test/core/client_channel/service_config_test.cc
@@ -0,0 +1,1045 @@
+/*
+ *
+ * Copyright 2019 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 <regex>
+
+#include <gtest/gtest.h>
+
+#include <grpc/grpc.h>
+#include "src/core/ext/filters/client_channel/resolver_result_parsing.h"
+#include "src/core/ext/filters/client_channel/service_config.h"
+#include "src/core/ext/filters/message_size/message_size_filter.h"
+#include "src/core/lib/gpr/string.h"
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+
+namespace grpc_core {
+namespace testing {
+
+class TestParsedObject1 : public ServiceConfig::ParsedConfig {
+ public:
+  TestParsedObject1(int value) : value_(value) {}
+
+  int value() const { return value_; }
+
+ private:
+  int value_;
+};
+
+class TestParser1 : public ServiceConfig::Parser {
+ public:
+  UniquePtr<ServiceConfig::ParsedConfig> ParseGlobalParams(
+      const grpc_json* json, grpc_error** error) override {
+    GPR_DEBUG_ASSERT(error != nullptr);
+    for (grpc_json* field = json->child; field != nullptr;
+         field = field->next) {
+      if (strcmp(field->key, "global_param") == 0) {
+        if (field->type != GRPC_JSON_NUMBER) {
+          *error =
+              GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidTypeErrorMessage());
+          return nullptr;
+        }
+        int value = gpr_parse_nonnegative_int(field->value);
+        if (value == -1) {
+          *error =
+              GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidValueErrorMessage());
+          return nullptr;
+        }
+        return UniquePtr<ServiceConfig::ParsedConfig>(
+            New<TestParsedObject1>(value));
+      }
+    }
+    return nullptr;
+  }
+
+  static const char* InvalidTypeErrorMessage() {
+    return "global_param value type should be a number";
+  }
+
+  static const char* InvalidValueErrorMessage() {
+    return "global_param value type should be non-negative";
+  }
+};
+
+class TestParser2 : public ServiceConfig::Parser {
+ public:
+  UniquePtr<ServiceConfig::ParsedConfig> ParsePerMethodParams(
+      const grpc_json* json, grpc_error** error) override {
+    GPR_DEBUG_ASSERT(error != nullptr);
+    for (grpc_json* field = json->child; field != nullptr;
+         field = field->next) {
+      if (field->key == nullptr || strcmp(field->key, "name") == 0) {
+        continue;
+      }
+      if (strcmp(field->key, "method_param") == 0) {
+        if (field->type != GRPC_JSON_NUMBER) {
+          *error =
+              GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidTypeErrorMessage());
+          return nullptr;
+        }
+        int value = gpr_parse_nonnegative_int(field->value);
+        if (value == -1) {
+          *error =
+              GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidValueErrorMessage());
+          return nullptr;
+        }
+        return UniquePtr<ServiceConfig::ParsedConfig>(
+            New<TestParsedObject1>(value));
+      }
+    }
+    return nullptr;
+  }
+
+  static const char* InvalidTypeErrorMessage() {
+    return "method_param value type should be a number";
+  }
+
+  static const char* InvalidValueErrorMessage() {
+    return "method_param value type should be non-negative";
+  }
+};
+
+// This parser always adds errors
+class ErrorParser : public ServiceConfig::Parser {
+ public:
+  UniquePtr<ServiceConfig::ParsedConfig> ParsePerMethodParams(
+      const grpc_json* json, grpc_error** error) override {
+    GPR_DEBUG_ASSERT(error != nullptr);
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(MethodError());
+    return nullptr;
+  }
+
+  UniquePtr<ServiceConfig::ParsedConfig> ParseGlobalParams(
+      const grpc_json* json, grpc_error** error) override {
+    GPR_DEBUG_ASSERT(error != nullptr);
+    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(GlobalError());
+    return nullptr;
+  }
+
+  static const char* MethodError() { return "ErrorParser : methodError"; }
+
+  static const char* GlobalError() { return "ErrorParser : globalError"; }
+};
+
+void VerifyRegexMatch(grpc_error* error, const std::regex& e) {
+  std::smatch match;
+  std::string s(grpc_error_string(error));
+  EXPECT_TRUE(std::regex_search(s, match, e));
+  GRPC_ERROR_UNREF(error);
+}
+
+class ServiceConfigTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ServiceConfig::Shutdown();
+    ServiceConfig::Init();
+    EXPECT_TRUE(ServiceConfig::RegisterParser(
+                    UniquePtr<ServiceConfig::Parser>(New<TestParser1>())) == 0);
+    EXPECT_TRUE(ServiceConfig::RegisterParser(
+                    UniquePtr<ServiceConfig::Parser>(New<TestParser2>())) == 1);
+  }
+};
+
+TEST_F(ServiceConfigTest, ErrorCheck1) {
+  const char* test_json = "";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string("failed to parse JSON for service config"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ServiceConfigTest, BasicTest1) {
+  const char* test_json = "{}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  EXPECT_TRUE(error == GRPC_ERROR_NONE);
+}
+
+TEST_F(ServiceConfigTest, ErrorNoNames) {
+  const char* test_json = "{\"methodConfig\": [{\"blah\":1}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Method "
+                  "Params)(.*)(referenced_errors)(.*)(No names "
+                  "found)(.*)(methodConfig)(.*)(referenced_errors)(.*)(No "
+                  "names specified)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ServiceConfigTest, ErrorNoNamesWithMultipleMethodConfigs) {
+  const char* test_json =
+      "{\"methodConfig\": [{}, {\"name\":[{\"service\":\"TestServ\"}]}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Method "
+                  "Params)(.*)(referenced_errors)(.*)(No names "
+                  "found)(.*)(methodConfig)(.*)(referenced_errors)(.*)(No "
+                  "names specified)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ServiceConfigTest, ValidMethodConfig) {
+  const char* test_json =
+      "{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}]}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  EXPECT_TRUE(error == GRPC_ERROR_NONE);
+}
+
+TEST_F(ServiceConfigTest, Parser1BasicTest1) {
+  const char* test_json = "{\"global_param\":5}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  EXPECT_TRUE((static_cast<TestParsedObject1*>(
+                   svc_cfg->GetParsedGlobalServiceConfigObject(0)))
+                  ->value() == 5);
+  EXPECT_TRUE(svc_cfg->GetMethodServiceConfigObjectsVector(
+                  grpc_slice_from_static_string("/TestServ/TestMethod")) ==
+              nullptr);
+}
+
+TEST_F(ServiceConfigTest, Parser1BasicTest2) {
+  const char* test_json = "{\"global_param\":1000}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  EXPECT_TRUE((static_cast<TestParsedObject1*>(
+                   svc_cfg->GetParsedGlobalServiceConfigObject(0)))
+                  ->value() == 1000);
+}
+
+TEST_F(ServiceConfigTest, Parser1ErrorInvalidType) {
+  const char* test_json = "{\"global_param\":\"5\"}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string("(Service config parsing "
+                           "error)(.*)(referenced_errors)(.*)(Global "
+                           "Params)(.*)(referenced_errors)(.*)") +
+               TestParser1::InvalidTypeErrorMessage());
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ServiceConfigTest, Parser1ErrorInvalidValue) {
+  const char* test_json = "{\"global_param\":-5}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string("(Service config parsing "
+                           "error)(.*)(referenced_errors)(.*)(Global "
+                           "Params)(.*)(referenced_errors)(.*)") +
+               TestParser1::InvalidValueErrorMessage());
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ServiceConfigTest, Parser2BasicTest) {
+  const char* test_json =
+      "{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], "
+      "\"method_param\":5}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* vector_ptr = svc_cfg->GetMethodServiceConfigObjectsVector(
+      grpc_slice_from_static_string("/TestServ/TestMethod"));
+  EXPECT_TRUE(vector_ptr != nullptr);
+  auto parsed_object = ((*vector_ptr)[1]).get();
+  EXPECT_TRUE(static_cast<TestParsedObject1*>(parsed_object)->value() == 5);
+}
+
+TEST_F(ServiceConfigTest, Parser2ErrorInvalidType) {
+  const char* test_json =
+      "{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], "
+      "\"method_param\":\"5\"}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  std::regex e(std::string("(Service config parsing "
+                           "error)(.*)(referenced_errors\":\\[)(.*)(Method "
+                           "Params)(.*)(referenced_errors)(.*)(methodConfig)("
+                           ".*)(referenced_errors)(.*)") +
+               TestParser2::InvalidTypeErrorMessage());
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ServiceConfigTest, Parser2ErrorInvalidValue) {
+  const char* test_json =
+      "{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], "
+      "\"method_param\":-5}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  std::regex e(std::string("(Service config parsing "
+                           "error)(.*)(referenced_errors\":\\[)(.*)(Method "
+                           "Params)(.*)(referenced_errors)()(.*)(methodConfig)("
+                           ".*)(referenced_errors)(.*)") +
+               TestParser2::InvalidValueErrorMessage());
+  VerifyRegexMatch(error, e);
+}
+
+// Test parsing with ErrorParsers which always add errors
+class ErroredParsersScopingTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ServiceConfig::Shutdown();
+    ServiceConfig::Init();
+    EXPECT_TRUE(ServiceConfig::RegisterParser(
+                    UniquePtr<ServiceConfig::Parser>(New<ErrorParser>())) == 0);
+    EXPECT_TRUE(ServiceConfig::RegisterParser(
+                    UniquePtr<ServiceConfig::Parser>(New<ErrorParser>())) == 1);
+  }
+};
+
+TEST_F(ErroredParsersScopingTest, GlobalParams) {
+  const char* test_json = "{}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  std::regex e(std::string("(Service config parsing "
+                           "error)(.*)(referenced_errors\":\\[)(.*)(Global "
+                           "Params)(.*)(referenced_errors)()(.*)") +
+               ErrorParser::GlobalError() + std::string("(.*)") +
+               ErrorParser::GlobalError());
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ErroredParsersScopingTest, MethodParams) {
+  const char* test_json = "{\"methodConfig\": [{}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors\":\\[)(.*)(Global "
+                  "Params)(.*)(referenced_errors)()(.*)") +
+      ErrorParser::GlobalError() + std::string("(.*)") +
+      ErrorParser::GlobalError() +
+      std::string("(.*)(Method "
+                  "Params)(.*)(referenced_errors)(.*)(field:methodConfig "
+                  "error:No names "
+                  "found)(.*)(methodConfig)(.*)(referenced_errors)(.*)") +
+      ErrorParser::MethodError() + std::string("(.*)") +
+      ErrorParser::MethodError() + std::string("(.*)(No names specified)"));
+  VerifyRegexMatch(error, e);
+}
+
+class ClientChannelParserTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ServiceConfig::Shutdown();
+    ServiceConfig::Init();
+    EXPECT_TRUE(
+        ServiceConfig::RegisterParser(UniquePtr<ServiceConfig::Parser>(
+            New<grpc_core::internal::ClientChannelServiceConfigParser>())) ==
+        0);
+  }
+};
+
+TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigPickFirst) {
+  const char* test_json = "{\"loadBalancingConfig\": [{\"pick_first\":{}}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  auto lb_config = parsed_object->parsed_lb_config();
+  EXPECT_TRUE(strcmp(lb_config->name(), "pick_first") == 0);
+}
+
+TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigRoundRobin) {
+  const char* test_json =
+      "{\"loadBalancingConfig\": [{\"round_robin\":{}}, {}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  auto parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  auto lb_config = parsed_object->parsed_lb_config();
+  EXPECT_TRUE(strcmp(lb_config->name(), "round_robin") == 0);
+}
+
+TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigGrpclb) {
+  const char* test_json =
+      "{\"loadBalancingConfig\": "
+      "[{\"grpclb\":{\"childPolicy\":[{\"pick_first\":{}}]}}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  auto lb_config = parsed_object->parsed_lb_config();
+  EXPECT_TRUE(strcmp(lb_config->name(), "grpclb") == 0);
+}
+
+TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigXds) {
+  const char* test_json =
+      "{\n"
+      "  \"loadBalancingConfig\":[\n"
+      "    { \"does_not_exist\":{} },\n"
+      "    { \"xds_experimental\":{ \"balancerName\": \"fake:///lb\" } }\n"
+      "  ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  auto lb_config = parsed_object->parsed_lb_config();
+  EXPECT_TRUE(strcmp(lb_config->name(), "xds_experimental") == 0);
+}
+
+TEST_F(ClientChannelParserTest, UnknownLoadBalancingConfig) {
+  const char* test_json = "{\"loadBalancingConfig\": [{\"unknown\":{}}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(field:"
+                  "loadBalancingConfig error:No known policy)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InvalidGrpclbLoadBalancingConfig) {
+  const char* test_json =
+      "{\"loadBalancingConfig\": "
+      "[{\"grpclb\":{\"childPolicy\":[{\"unknown\":{}}]}}]}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(GrpcLb "
+                  "Parser)(.*)(referenced_errors)(.*)(field:childPolicy "
+                  "error:No known policy)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InalidLoadBalancingConfigXds) {
+  const char* test_json =
+      "{\n"
+      "  \"loadBalancingConfig\":[\n"
+      "    { \"does_not_exist\":{} },\n"
+      "    { \"xds_experimental\":{} }\n"
+      "  ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(Xds "
+                  "Parser)(.*)(referenced_errors)(.*)(field:balancerName "
+                  "error:not found)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, ValidLoadBalancingPolicy) {
+  const char* test_json = "{\"loadBalancingPolicy\":\"pick_first\"}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  const auto* lb_policy = parsed_object->parsed_deprecated_lb_policy();
+  ASSERT_TRUE(lb_policy != nullptr);
+  EXPECT_TRUE(strcmp(lb_policy, "pick_first") == 0);
+}
+
+TEST_F(ClientChannelParserTest, ValidLoadBalancingPolicyAllCaps) {
+  const char* test_json = "{\"loadBalancingPolicy\":\"PICK_FIRST\"}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  const auto* lb_policy = parsed_object->parsed_deprecated_lb_policy();
+  ASSERT_TRUE(lb_policy != nullptr);
+  EXPECT_TRUE(strcmp(lb_policy, "pick_first") == 0);
+}
+
+TEST_F(ClientChannelParserTest, UnknownLoadBalancingPolicy) {
+  const char* test_json = "{\"loadBalancingPolicy\":\"unknown\"}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(field:"
+                  "loadBalancingPolicy error:Unknown lb policy)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, LoadBalancingPolicyXdsNotAllowed) {
+  const char* test_json = "{\"loadBalancingPolicy\":\"xds_experimental\"}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(field:"
+                  "loadBalancingPolicy error:xds_experimental requires a "
+                  "config. Please use loadBalancingConfig instead.)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, ValidRetryThrottling) {
+  const char* test_json =
+      "{\n"
+      "  \"retryThrottling\": {\n"
+      "    \"maxTokens\": 2,\n"
+      "    \"tokenRatio\": 1.0\n"
+      "  }\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  const auto retryThrottling = parsed_object->retry_throttling();
+  ASSERT_TRUE(retryThrottling.has_value());
+  EXPECT_EQ(retryThrottling.value().max_milli_tokens, 2000);
+  EXPECT_EQ(retryThrottling.value().milli_token_ratio, 1000);
+}
+
+TEST_F(ClientChannelParserTest, RetryThrottlingMissingFields) {
+  const char* test_json =
+      "{\n"
+      "  \"retryThrottling\": {\n"
+      "  }\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(field:retryThrottling "
+                  "field:maxTokens error:Not found)(.*)(field:retryThrottling "
+                  "field:tokenRatio error:Not found)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InvalidRetryThrottlingNegativeMaxTokens) {
+  const char* test_json =
+      "{\n"
+      "  \"retryThrottling\": {\n"
+      "    \"maxTokens\": -2,\n"
+      "    \"tokenRatio\": 1.0\n"
+      "  }\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(field:retryThrottling "
+                  "field:maxTokens error:should be greater than zero)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InvalidRetryThrottlingInvalidTokenRatio) {
+  const char* test_json =
+      "{\n"
+      "  \"retryThrottling\": {\n"
+      "    \"maxTokens\": 2,\n"
+      "    \"tokenRatio\": -1\n"
+      "  }\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(Client channel global "
+                  "parser)(.*)(referenced_errors)(.*)(field:retryThrottling "
+                  "field:tokenRatio error:Failed parsing)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, ValidTimeout) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"timeout\": \"5s\"\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* vector_ptr = svc_cfg->GetMethodServiceConfigObjectsVector(
+      grpc_slice_from_static_string("/TestServ/TestMethod"));
+  EXPECT_TRUE(vector_ptr != nullptr);
+  auto parsed_object = ((*vector_ptr)[0]).get();
+  EXPECT_EQ((static_cast<grpc_core::internal::ClientChannelMethodParsedObject*>(
+                 parsed_object))
+                ->timeout(),
+            5000);
+}
+
+TEST_F(ClientChannelParserTest, InvalidTimeout) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"service\", \"method\": \"method\" }\n"
+      "    ],\n"
+      "    \"timeout\": \"5sec\"\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Method "
+                  "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)("
+                  "referenced_errors)(.*)(Client channel "
+                  "parser)(.*)(referenced_errors)(.*)(field:timeout "
+                  "error:Failed parsing)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, ValidWaitForReady) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"waitForReady\": true\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* vector_ptr = svc_cfg->GetMethodServiceConfigObjectsVector(
+      grpc_slice_from_static_string("/TestServ/TestMethod"));
+  EXPECT_TRUE(vector_ptr != nullptr);
+  auto parsed_object = ((*vector_ptr)[0]).get();
+  EXPECT_TRUE(
+      (static_cast<grpc_core::internal::ClientChannelMethodParsedObject*>(
+           parsed_object))
+          ->wait_for_ready()
+          .has_value());
+  EXPECT_TRUE(
+      (static_cast<grpc_core::internal::ClientChannelMethodParsedObject*>(
+           parsed_object))
+          ->wait_for_ready()
+          .value());
+}
+
+TEST_F(ClientChannelParserTest, InvalidWaitForReady) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"service\", \"method\": \"method\" }\n"
+      "    ],\n"
+      "    \"waitForReady\": \"true\"\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Method "
+                  "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)("
+                  "referenced_errors)(.*)(Client channel "
+                  "parser)(.*)(referenced_errors)(.*)(field:waitForReady "
+                  "error:Type should be true/false)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, ValidRetryPolicy) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"retryPolicy\": {\n"
+      "      \"maxAttempts\": 3,\n"
+      "      \"initialBackoff\": \"1s\",\n"
+      "      \"maxBackoff\": \"120s\",\n"
+      "      \"backoffMultiplier\": 1.6,\n"
+      "      \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
+      "    }\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* vector_ptr = svc_cfg->GetMethodServiceConfigObjectsVector(
+      grpc_slice_from_static_string("/TestServ/TestMethod"));
+  EXPECT_TRUE(vector_ptr != nullptr);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelMethodParsedObject*>(
+          ((*vector_ptr)[0]).get());
+  EXPECT_TRUE(parsed_object->retry_policy() != nullptr);
+  EXPECT_EQ(parsed_object->retry_policy()->max_attempts, 3);
+  EXPECT_EQ(parsed_object->retry_policy()->initial_backoff, 1000);
+  EXPECT_EQ(parsed_object->retry_policy()->max_backoff, 120000);
+  EXPECT_EQ(parsed_object->retry_policy()->backoff_multiplier, 1.6f);
+  EXPECT_TRUE(parsed_object->retry_policy()->retryable_status_codes.Contains(
+      GRPC_STATUS_ABORTED));
+}
+
+TEST_F(ClientChannelParserTest, InvalidRetryPolicyMaxAttempts) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"retryPolicy\": {\n"
+      "      \"maxAttempts\": 1,\n"
+      "      \"initialBackoff\": \"1s\",\n"
+      "      \"maxBackoff\": \"120s\",\n"
+      "      \"backoffMultiplier\": 1.6,\n"
+      "      \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
+      "    }\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string(
+      "(Service config parsing "
+      "error)(.*)(referenced_errors)(.*)(Method "
+      "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)(referenced_errors)("
+      ".*)(Client channel "
+      "parser)(.*)(referenced_errors)(.*)(retryPolicy)(.*)(referenced_errors)(."
+      "*)(field:maxAttempts error:should be at least 2)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InvalidRetryPolicyInitialBackoff) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"retryPolicy\": {\n"
+      "      \"maxAttempts\": 1,\n"
+      "      \"initialBackoff\": \"1sec\",\n"
+      "      \"maxBackoff\": \"120s\",\n"
+      "      \"backoffMultiplier\": 1.6,\n"
+      "      \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
+      "    }\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string(
+      "(Service config parsing "
+      "error)(.*)(referenced_errors)(.*)(Method "
+      "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)(referenced_errors)("
+      ".*)(Client channel "
+      "parser)(.*)(referenced_errors)(.*)(retryPolicy)(.*)(referenced_errors)(."
+      "*)(field:initialBackoff error:Failed to parse)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InvalidRetryPolicyMaxBackoff) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"retryPolicy\": {\n"
+      "      \"maxAttempts\": 1,\n"
+      "      \"initialBackoff\": \"1s\",\n"
+      "      \"maxBackoff\": \"120sec\",\n"
+      "      \"backoffMultiplier\": 1.6,\n"
+      "      \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
+      "    }\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string(
+      "(Service config parsing "
+      "error)(.*)(referenced_errors)(.*)(Method "
+      "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)(referenced_errors)("
+      ".*)(Client channel "
+      "parser)(.*)(referenced_errors)(.*)(retryPolicy)(.*)(referenced_errors)(."
+      "*)(field:maxBackoff error:failed to parse)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InvalidRetryPolicyBackoffMultiplier) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"retryPolicy\": {\n"
+      "      \"maxAttempts\": 1,\n"
+      "      \"initialBackoff\": \"1s\",\n"
+      "      \"maxBackoff\": \"120s\",\n"
+      "      \"backoffMultiplier\": \"1.6\",\n"
+      "      \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
+      "    }\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string(
+      "(Service config parsing "
+      "error)(.*)(referenced_errors)(.*)(Method "
+      "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)(referenced_errors)("
+      ".*)(Client channel "
+      "parser)(.*)(referenced_errors)(.*)(retryPolicy)(.*)(referenced_errors)(."
+      "*)(field:backoffMultiplier error:should be of type number)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, InvalidRetryPolicyRetryableStatusCodes) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"retryPolicy\": {\n"
+      "      \"maxAttempts\": 1,\n"
+      "      \"initialBackoff\": \"1s\",\n"
+      "      \"maxBackoff\": \"120s\",\n"
+      "      \"backoffMultiplier\": \"1.6\",\n"
+      "      \"retryableStatusCodes\": []\n"
+      "    }\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(std::string(
+      "(Service config parsing "
+      "error)(.*)(referenced_errors)(.*)(Method "
+      "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)(referenced_errors)("
+      ".*)(Client channel "
+      "parser)(.*)(referenced_errors)(.*)(retryPolicy)(.*)(referenced_errors)(."
+      "*)(field:retryableStatusCodes error:should be non-empty)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(ClientChannelParserTest, ValidHealthCheck) {
+  const char* test_json =
+      "{\n"
+      "  \"healthCheckConfig\": {\n"
+      "    \"serviceName\": \"health_check_service_name\"\n"
+      "    }\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* parsed_object =
+      static_cast<grpc_core::internal::ClientChannelGlobalParsedObject*>(
+          svc_cfg->GetParsedGlobalServiceConfigObject(0));
+  ASSERT_TRUE(parsed_object != nullptr);
+  EXPECT_EQ(strcmp(parsed_object->health_check_service_name(),
+                   "health_check_service_name"),
+            0);
+}
+
+TEST_F(ClientChannelParserTest, InvalidHealthCheckMultipleEntries) {
+  const char* test_json =
+      "{\n"
+      "  \"healthCheckConfig\": {\n"
+      "    \"serviceName\": \"health_check_service_name\"\n"
+      "    },\n"
+      "  \"healthCheckConfig\": {\n"
+      "    \"serviceName\": \"health_check_service_name1\"\n"
+      "    }\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Global "
+                  "Params)(.*)(referenced_errors)(.*)(field:healthCheckConfig "
+                  "error:Duplicate entry)"));
+  std::smatch match;
+  std::string s(grpc_error_string(error));
+  EXPECT_TRUE(std::regex_search(s, match, e));
+  GRPC_ERROR_UNREF(error);
+}
+
+class MessageSizeParserTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ServiceConfig::Shutdown();
+    ServiceConfig::Init();
+    EXPECT_TRUE(ServiceConfig::RegisterParser(UniquePtr<ServiceConfig::Parser>(
+                    New<MessageSizeParser>())) == 0);
+  }
+};
+
+TEST_F(MessageSizeParserTest, Valid) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"maxRequestMessageBytes\": 1024,\n"
+      "    \"maxResponseMessageBytes\": 1024\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error == GRPC_ERROR_NONE);
+  const auto* vector_ptr = svc_cfg->GetMethodServiceConfigObjectsVector(
+      grpc_slice_from_static_string("/TestServ/TestMethod"));
+  EXPECT_TRUE(vector_ptr != nullptr);
+  auto parsed_object =
+      static_cast<MessageSizeParsedObject*>(((*vector_ptr)[0]).get());
+  ASSERT_TRUE(parsed_object != nullptr);
+  EXPECT_EQ(parsed_object->limits().max_send_size, 1024);
+  EXPECT_EQ(parsed_object->limits().max_recv_size, 1024);
+}
+
+TEST_F(MessageSizeParserTest, InvalidMaxRequestMessageBytes) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"maxRequestMessageBytes\": -1024\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Method "
+                  "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)("
+                  "referenced_errors)(.*)(Message size "
+                  "parser)(.*)(referenced_errors)(.*)(field:"
+                  "maxRequestMessageBytes error:should be non-negative)"));
+  VerifyRegexMatch(error, e);
+}
+
+TEST_F(MessageSizeParserTest, InvalidMaxResponseMessageBytes) {
+  const char* test_json =
+      "{\n"
+      "  \"methodConfig\": [ {\n"
+      "    \"name\": [\n"
+      "      { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
+      "    ],\n"
+      "    \"maxResponseMessageBytes\": {}\n"
+      "  } ]\n"
+      "}";
+  grpc_error* error = GRPC_ERROR_NONE;
+  auto svc_cfg = ServiceConfig::Create(test_json, &error);
+  gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
+  ASSERT_TRUE(error != GRPC_ERROR_NONE);
+  std::regex e(
+      std::string("(Service config parsing "
+                  "error)(.*)(referenced_errors)(.*)(Method "
+                  "Params)(.*)(referenced_errors)(.*)(methodConfig)(.*)("
+                  "referenced_errors)(.*)(Message size "
+                  "parser)(.*)(referenced_errors)(.*)(field:"
+                  "maxResponseMessageBytes error:should be of type number)"));
+  VerifyRegexMatch(error, e);
+}
+
+}  // namespace testing
+}  // namespace grpc_core
+
+int main(int argc, char** argv) {
+  grpc::testing::TestEnvironment env(argc, argv);
+  grpc_init();
+  ::testing::InitGoogleTest(&argc, argv);
+  int ret = RUN_ALL_TESTS();
+  grpc_shutdown();
+  return ret;
+}
diff --git a/test/core/compression/compression_test.cc b/test/core/compression/compression_test.cc
index 6522988..cf6d188 100644
--- a/test/core/compression/compression_test.cc
+++ b/test/core/compression/compression_test.cc
@@ -23,7 +23,10 @@
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
 
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
 #include "test/core/util/test_config.h"
 
 static void test_compression_algorithm_parse(void) {
@@ -258,12 +261,88 @@
   }
 }
 
+static void test_channel_args_set_compression_algorithm(void) {
+  grpc_core::ExecCtx exec_ctx;
+  grpc_channel_args* ch_args;
+
+  ch_args =
+      grpc_channel_args_set_compression_algorithm(nullptr, GRPC_COMPRESS_GZIP);
+  GPR_ASSERT(ch_args->num_args == 1);
+  GPR_ASSERT(strcmp(ch_args->args[0].key,
+                    GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM) == 0);
+  GPR_ASSERT(ch_args->args[0].type == GRPC_ARG_INTEGER);
+
+  grpc_channel_args_destroy(ch_args);
+}
+
+static void test_channel_args_compression_algorithm_states(void) {
+  grpc_core::ExecCtx exec_ctx;
+  grpc_channel_args *ch_args, *ch_args_wo_gzip, *ch_args_wo_gzip_deflate,
+      *ch_args_wo_gzip_deflate_gzip;
+  unsigned states_bitset;
+  size_t i;
+
+  ch_args = grpc_channel_args_copy_and_add(nullptr, nullptr, 0);
+  /* by default, all enabled */
+  states_bitset = static_cast<unsigned>(
+      grpc_channel_args_compression_algorithm_get_states(ch_args));
+
+  for (i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
+    GPR_ASSERT(GPR_BITGET(states_bitset, i));
+  }
+
+  /* disable gzip and deflate and stream/gzip */
+  ch_args_wo_gzip = grpc_channel_args_compression_algorithm_set_state(
+      &ch_args, GRPC_COMPRESS_GZIP, 0);
+  GPR_ASSERT(ch_args == ch_args_wo_gzip);
+  ch_args_wo_gzip_deflate = grpc_channel_args_compression_algorithm_set_state(
+      &ch_args_wo_gzip, GRPC_COMPRESS_DEFLATE, 0);
+  GPR_ASSERT(ch_args_wo_gzip == ch_args_wo_gzip_deflate);
+  ch_args_wo_gzip_deflate_gzip =
+      grpc_channel_args_compression_algorithm_set_state(
+          &ch_args_wo_gzip_deflate, GRPC_COMPRESS_STREAM_GZIP, 0);
+  GPR_ASSERT(ch_args_wo_gzip_deflate == ch_args_wo_gzip_deflate_gzip);
+
+  states_bitset =
+      static_cast<unsigned>(grpc_channel_args_compression_algorithm_get_states(
+          ch_args_wo_gzip_deflate));
+  for (i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
+    if (i == GRPC_COMPRESS_GZIP || i == GRPC_COMPRESS_DEFLATE ||
+        i == GRPC_COMPRESS_STREAM_GZIP) {
+      GPR_ASSERT(GPR_BITGET(states_bitset, i) == 0);
+    } else {
+      GPR_ASSERT(GPR_BITGET(states_bitset, i) != 0);
+    }
+  }
+
+  /* re-enabled gzip and stream/gzip only */
+  ch_args_wo_gzip = grpc_channel_args_compression_algorithm_set_state(
+      &ch_args_wo_gzip_deflate_gzip, GRPC_COMPRESS_GZIP, 1);
+  ch_args_wo_gzip = grpc_channel_args_compression_algorithm_set_state(
+      &ch_args_wo_gzip, GRPC_COMPRESS_STREAM_GZIP, 1);
+  GPR_ASSERT(ch_args_wo_gzip == ch_args_wo_gzip_deflate_gzip);
+
+  states_bitset = static_cast<unsigned>(
+      grpc_channel_args_compression_algorithm_get_states(ch_args_wo_gzip));
+  for (i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
+    if (i == GRPC_COMPRESS_DEFLATE) {
+      GPR_ASSERT(GPR_BITGET(states_bitset, i) == 0);
+    } else {
+      GPR_ASSERT(GPR_BITGET(states_bitset, i) != 0);
+    }
+  }
+
+  grpc_channel_args_destroy(ch_args);
+}
+
 int main(int argc, char** argv) {
   grpc_init();
   test_compression_algorithm_parse();
   test_compression_algorithm_name();
   test_compression_algorithm_for_level();
   test_compression_enable_disable_algorithm();
+  test_channel_args_set_compression_algorithm();
+  test_channel_args_compression_algorithm_states();
   grpc_shutdown();
 
   return 0;
diff --git a/test/core/end2end/bad_server_response_test.cc b/test/core/end2end/bad_server_response_test.cc
index 3701a93..2d74b6b 100644
--- a/test/core/end2end/bad_server_response_test.cc
+++ b/test/core/end2end/bad_server_response_test.cc
@@ -250,7 +250,7 @@
     if (done || gpr_time_cmp(time_left, gpr_time_0(GPR_TIMESPAN)) < 0) {
       break;
     }
-    test_tcp_server_poll(pa->server, 1);
+    test_tcp_server_poll(pa->server, 1000);
   }
   gpr_event_set(pa->signal_when_done, (void*)1);
   gpr_free(pa);
diff --git a/test/core/end2end/fixtures/h2_compress.cc b/test/core/end2end/fixtures/h2_compress.cc
index 04142da..f97192f 100644
--- a/test/core/end2end/fixtures/h2_compress.cc
+++ b/test/core/end2end/fixtures/h2_compress.cc
@@ -29,6 +29,7 @@
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/connected_channel.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/surface/channel.h"
 #include "src/core/lib/surface/server.h"
diff --git a/test/core/end2end/fixtures/h2_full+trace.cc b/test/core/end2end/fixtures/h2_full+trace.cc
index ce8f6bf..b8dbe26 100644
--- a/test/core/end2end/fixtures/h2_full+trace.cc
+++ b/test/core/end2end/fixtures/h2_full+trace.cc
@@ -33,7 +33,7 @@
 #include "src/core/ext/filters/http/server/http_server_filter.h"
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/connected_channel.h"
-#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/debug/trace.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/surface/channel.h"
 #include "src/core/lib/surface/server.h"
@@ -105,7 +105,7 @@
 
   /* force tracing on, with a value to force many
      code paths in trace.c to be taken */
-  gpr_setenv("GRPC_TRACE", "doesnt-exist,http,all");
+  GPR_GLOBAL_CONFIG_SET(grpc_trace, "doesnt-exist,http,all");
 
 #ifdef GRPC_POSIX_SOCKET
   g_fixture_slowdown_factor = isatty(STDOUT_FILENO) ? 10 : 1;
diff --git a/test/core/end2end/fixtures/h2_sockpair+trace.cc b/test/core/end2end/fixtures/h2_sockpair+trace.cc
index 4494d5c..7954bc1 100644
--- a/test/core/end2end/fixtures/h2_sockpair+trace.cc
+++ b/test/core/end2end/fixtures/h2_sockpair+trace.cc
@@ -35,7 +35,7 @@
 #include "src/core/ext/filters/http/server/http_server_filter.h"
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/connected_channel.h"
-#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/debug/trace.h"
 #include "src/core/lib/iomgr/endpoint_pair.h"
 #include "src/core/lib/iomgr/iomgr.h"
 #include "src/core/lib/surface/channel.h"
@@ -133,7 +133,8 @@
 
   /* force tracing on, with a value to force many
      code paths in trace.c to be taken */
-  gpr_setenv("GRPC_TRACE", "doesnt-exist,http,all");
+  GPR_GLOBAL_CONFIG_SET(grpc_trace, "doesnt-exist,http,all");
+
 #ifdef GRPC_POSIX_SOCKET
   g_fixture_slowdown_factor = isatty(STDOUT_FILENO) ? 10 : 1;
 #else
diff --git a/test/core/end2end/fixtures/h2_spiffe.cc b/test/core/end2end/fixtures/h2_spiffe.cc
index 9ab796e..cdf091b 100644
--- a/test/core/end2end/fixtures/h2_spiffe.cc
+++ b/test/core/end2end/fixtures/h2_spiffe.cc
@@ -35,6 +35,7 @@
 #include "src/core/lib/gprpp/thd.h"
 #include "src/core/lib/security/credentials/credentials.h"
 #include "src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
@@ -277,7 +278,7 @@
   GPR_ASSERT(roots_file != nullptr);
   GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
   fclose(roots_file);
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
   grpc_init();
   for (size_t ind = 0; ind < sizeof(configs) / sizeof(*configs); ind++) {
     grpc_end2end_tests(argc, argv, configs[ind]);
diff --git a/test/core/end2end/fixtures/h2_ssl.cc b/test/core/end2end/fixtures/h2_ssl.cc
index 1fcd785..3fc9bc7 100644
--- a/test/core/end2end/fixtures/h2_ssl.cc
+++ b/test/core/end2end/fixtures/h2_ssl.cc
@@ -25,11 +25,11 @@
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
 #include "src/core/lib/security/credentials/credentials.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
@@ -167,7 +167,7 @@
   GPR_ASSERT(roots_file != nullptr);
   GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
   fclose(roots_file);
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
 
   grpc_init();
 
diff --git a/test/core/end2end/fixtures/h2_ssl_cred_reload.cc b/test/core/end2end/fixtures/h2_ssl_cred_reload.cc
new file mode 100644
index 0000000..1d54a43
--- /dev/null
+++ b/test/core/end2end/fixtures/h2_ssl_cred_reload.cc
@@ -0,0 +1,208 @@
+/*
+ *
+ * Copyright 2015 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "test/core/end2end/end2end_tests.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gpr/host_port.h"
+#include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gpr/tmpfile.h"
+#include "src/core/lib/security/credentials/credentials.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
+#include "test/core/end2end/data/ssl_test_data.h"
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+
+typedef struct fullstack_secure_fixture_data {
+  char* localaddr;
+  bool server_credential_reloaded;
+} fullstack_secure_fixture_data;
+
+static grpc_ssl_certificate_config_reload_status
+ssl_server_certificate_config_callback(
+    void* user_data, grpc_ssl_server_certificate_config** config) {
+  if (config == nullptr) {
+    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_FAIL;
+  }
+  fullstack_secure_fixture_data* ffd =
+      static_cast<fullstack_secure_fixture_data*>(user_data);
+  if (!ffd->server_credential_reloaded) {
+    grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {test_server1_key,
+                                                    test_server1_cert};
+    *config = grpc_ssl_server_certificate_config_create(test_root_cert,
+                                                        &pem_key_cert_pair, 1);
+    ffd->server_credential_reloaded = true;
+    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_NEW;
+  } else {
+    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_UNCHANGED;
+  }
+}
+
+static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
+  grpc_end2end_test_fixture f;
+  int port = grpc_pick_unused_port_or_die();
+  fullstack_secure_fixture_data* ffd =
+      static_cast<fullstack_secure_fixture_data*>(
+          gpr_malloc(sizeof(fullstack_secure_fixture_data)));
+  memset(&f, 0, sizeof(f));
+  gpr_join_host_port(&ffd->localaddr, "localhost", port);
+
+  f.fixture_data = ffd;
+  f.cq = grpc_completion_queue_create_for_next(nullptr);
+  f.shutdown_cq = grpc_completion_queue_create_for_pluck(nullptr);
+
+  return f;
+}
+
+static void process_auth_failure(void* state, grpc_auth_context* ctx,
+                                 const grpc_metadata* md, size_t md_count,
+                                 grpc_process_auth_metadata_done_cb cb,
+                                 void* user_data) {
+  GPR_ASSERT(state == nullptr);
+  cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
+}
+
+static void chttp2_init_client_secure_fullstack(
+    grpc_end2end_test_fixture* f, grpc_channel_args* client_args,
+    grpc_channel_credentials* creds) {
+  fullstack_secure_fixture_data* ffd =
+      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
+  f->client =
+      grpc_secure_channel_create(creds, ffd->localaddr, client_args, nullptr);
+  GPR_ASSERT(f->client != nullptr);
+  grpc_channel_credentials_release(creds);
+}
+
+static void chttp2_init_server_secure_fullstack(
+    grpc_end2end_test_fixture* f, grpc_channel_args* server_args,
+    grpc_server_credentials* server_creds) {
+  fullstack_secure_fixture_data* ffd =
+      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
+  if (f->server) {
+    grpc_server_destroy(f->server);
+  }
+  ffd->server_credential_reloaded = false;
+  f->server = grpc_server_create(server_args, nullptr);
+  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
+  GPR_ASSERT(grpc_server_add_secure_http2_port(f->server, ffd->localaddr,
+                                               server_creds));
+  grpc_server_credentials_release(server_creds);
+  grpc_server_start(f->server);
+}
+
+void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
+  fullstack_secure_fixture_data* ffd =
+      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
+  gpr_free(ffd->localaddr);
+  gpr_free(ffd);
+}
+
+static void chttp2_init_client_simple_ssl_secure_fullstack(
+    grpc_end2end_test_fixture* f, grpc_channel_args* client_args) {
+  grpc_channel_credentials* ssl_creds =
+      grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
+  grpc_arg ssl_name_override = {
+      GRPC_ARG_STRING,
+      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
+      {const_cast<char*>("foo.test.google.fr")}};
+  grpc_channel_args* new_client_args =
+      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
+  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);
+  grpc_channel_args_destroy(new_client_args);
+}
+
+static int fail_server_auth_check(grpc_channel_args* server_args) {
+  size_t i;
+  if (server_args == nullptr) return 0;
+  for (i = 0; i < server_args->num_args; i++) {
+    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
+        0) {
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static void chttp2_init_server_simple_ssl_secure_fullstack(
+    grpc_end2end_test_fixture* f, grpc_channel_args* server_args) {
+  grpc_ssl_server_credentials_options* options =
+      grpc_ssl_server_credentials_create_options_using_config_fetcher(
+          GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
+          ssl_server_certificate_config_callback, f->fixture_data);
+  grpc_server_credentials* ssl_creds =
+      grpc_ssl_server_credentials_create_with_options(options);
+  if (fail_server_auth_check(server_args)) {
+    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
+                                              nullptr};
+    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+  }
+  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
+}
+
+/* All test configurations */
+
+static grpc_end2end_test_config configs[] = {
+    {"chttp2/simple_ssl_fullstack",
+     FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
+         FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
+         FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
+         FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
+     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack,
+     chttp2_init_client_simple_ssl_secure_fullstack,
+     chttp2_init_server_simple_ssl_secure_fullstack,
+     chttp2_tear_down_secure_fullstack},
+};
+
+int main(int argc, char** argv) {
+  size_t i;
+  FILE* roots_file;
+  size_t roots_size = strlen(test_root_cert);
+  char* roots_filename;
+
+  grpc::testing::TestEnvironment env(argc, argv);
+  grpc_end2end_tests_pre_init();
+
+  /* Set the SSL roots env var. */
+  roots_file = gpr_tmpfile("chttp2_simple_ssl_fullstack_test", &roots_filename);
+  GPR_ASSERT(roots_filename != nullptr);
+  GPR_ASSERT(roots_file != nullptr);
+  GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
+  fclose(roots_file);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
+
+  grpc_init();
+
+  for (i = 0; i < sizeof(configs) / sizeof(*configs); i++) {
+    grpc_end2end_tests(argc, argv, configs[i]);
+  }
+
+  grpc_shutdown();
+
+  /* Cleanup. */
+  remove(roots_filename);
+  gpr_free(roots_filename);
+
+  return 0;
+}
diff --git a/test/core/end2end/fixtures/h2_ssl_proxy.cc b/test/core/end2end/fixtures/h2_ssl_proxy.cc
index f185807..d5f695b 100644
--- a/test/core/end2end/fixtures/h2_ssl_proxy.cc
+++ b/test/core/end2end/fixtures/h2_ssl_proxy.cc
@@ -25,11 +25,11 @@
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
 #include "src/core/lib/security/credentials/credentials.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 #include "test/core/end2end/fixtures/proxy.h"
 #include "test/core/util/port.h"
@@ -208,7 +208,7 @@
   GPR_ASSERT(roots_file != nullptr);
   GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
   fclose(roots_file);
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
 
   grpc_init();
 
diff --git a/test/core/end2end/gen_build_yaml.py b/test/core/end2end/gen_build_yaml.py
index 231d226..7bb802a 100755
--- a/test/core/end2end/gen_build_yaml.py
+++ b/test/core/end2end/gen_build_yaml.py
@@ -74,6 +74,7 @@
     'h2_sockpair+trace': socketpair_unsecure_fixture_options._replace(
         ci_mac=False, tracing=True, large_writes=False, exclude_iomgrs=['uv']),
     'h2_ssl': default_secure_fixture_options,
+    'h2_ssl_cred_reload': default_secure_fixture_options,
     'h2_spiffe': default_secure_fixture_options,
     'h2_local_uds': local_fixture_options,
     'h2_local_ipv4': local_fixture_options,
diff --git a/test/core/end2end/generate_tests.bzl b/test/core/end2end/generate_tests.bzl
index b3c58bf..85faca1 100755
--- a/test/core/end2end/generate_tests.bzl
+++ b/test/core/end2end/generate_tests.bzl
@@ -87,6 +87,7 @@
         client_channel = False,
     ),
     "h2_ssl": _fixture_options(secure = True),
+    "h2_ssl_cred_reload": _fixture_options(secure = True),
     "h2_spiffe": _fixture_options(secure = True),
     "h2_local_uds": _fixture_options(secure = True, dns_resolver = False, _platforms = ["linux", "mac", "posix"]),
     "h2_local_ipv4": _fixture_options(secure = True, dns_resolver = False, _platforms = ["linux", "mac", "posix"]),
@@ -150,6 +151,7 @@
         client_channel = False,
     ),
     "h2_ssl": _fixture_options(secure = False),
+    "h2_ssl_cred_reload": _fixture_options(secure = False),
     "h2_ssl_proxy": _fixture_options(includes_proxy = True, secure = False),
     "h2_uds": _fixture_options(
         dns_resolver = False,
@@ -385,6 +387,7 @@
             ":proxy",
             ":local_util",
         ],
+        tags = ["no_windows"],
     )
 
     for f, fopt in END2END_FIXTURES.items():
@@ -398,6 +401,7 @@
                 "//:grpc",
                 "//:gpr",
             ],
+            tags = ["no_windows"],
         )
         for t, topt in END2END_TESTS.items():
             #print(_compatible(fopt, topt), f, t, fopt, topt)
@@ -413,6 +417,7 @@
                         t,
                         poller,
                     ],
+                    tags = ["no_windows"],
                 )
 
 def grpc_end2end_nosec_tests():
@@ -435,6 +440,7 @@
             ":proxy",
             ":local_util",
         ],
+        tags = ["no_windows"],
     )
 
     for f, fopt in END2END_NOSEC_FIXTURES.items():
@@ -450,6 +456,7 @@
                 "//:grpc_unsecure",
                 "//:gpr",
             ],
+            tags = ["no_windows"],
         )
         for t, topt in END2END_TESTS.items():
             #print(_compatible(fopt, topt), f, t, fopt, topt)
diff --git a/test/core/end2end/h2_ssl_cert_test.cc b/test/core/end2end/h2_ssl_cert_test.cc
index cb0800b..e928577 100644
--- a/test/core/end2end/h2_ssl_cert_test.cc
+++ b/test/core/end2end/h2_ssl_cert_test.cc
@@ -25,11 +25,11 @@
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
 #include "src/core/lib/security/credentials/credentials.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 #include "test/core/util/port.h"
@@ -366,7 +366,7 @@
   GPR_ASSERT(roots_file != nullptr);
   GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
   fclose(roots_file);
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
 
   grpc_init();
   ::testing::InitGoogleTest(&argc, argv);
diff --git a/test/core/end2end/h2_ssl_session_reuse_test.cc b/test/core/end2end/h2_ssl_session_reuse_test.cc
index fbcdcc4..b2d0a5e 100644
--- a/test/core/end2end/h2_ssl_session_reuse_test.cc
+++ b/test/core/end2end/h2_ssl_session_reuse_test.cc
@@ -25,11 +25,11 @@
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
 #include "src/core/lib/security/credentials/credentials.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 #include "test/core/util/port.h"
@@ -265,7 +265,7 @@
   GPR_ASSERT(roots_file != nullptr);
   GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
   fclose(roots_file);
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
 
   grpc_init();
   ::testing::InitGoogleTest(&argc, argv);
diff --git a/test/core/end2end/tests/compressed_payload.cc b/test/core/end2end/tests/compressed_payload.cc
index 178d68c..2b9ab5d 100644
--- a/test/core/end2end/tests/compressed_payload.cc
+++ b/test/core/end2end/tests/compressed_payload.cc
@@ -30,6 +30,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/surface/call.h"
 #include "src/core/lib/surface/call_test_only.h"
 #include "src/core/lib/transport/static_metadata.h"
diff --git a/test/core/end2end/tests/keepalive_timeout.cc b/test/core/end2end/tests/keepalive_timeout.cc
index 3c33f04..1750f6f 100644
--- a/test/core/end2end/tests/keepalive_timeout.cc
+++ b/test/core/end2end/tests/keepalive_timeout.cc
@@ -28,11 +28,15 @@
 
 #include "src/core/ext/transport/chttp2/transport/frame_ping.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
+#include "src/core/lib/iomgr/iomgr.h"
 #include "test/core/end2end/cq_verifier.h"
 
+#ifdef GRPC_POSIX_SOCKET
+#include "src/core/lib/iomgr/ev_posix.h"
+#endif  // GRPC_POSIX_SOCKET
+
 static void* tag(intptr_t t) { return (void*)t; }
 
 static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
@@ -225,13 +229,13 @@
  * 200ms. In the success case, each ping ack should reset the keepalive timer so
  * that the keepalive ping is never sent. */
 static void test_read_delays_keepalive(grpc_end2end_test_config config) {
-  char* poller = gpr_getenv("GRPC_POLL_STRATEGY");
+#ifdef GRPC_POSIX_SOCKET
+  grpc_core::UniquePtr<char> poller = GPR_GLOBAL_CONFIG_GET(grpc_poll_strategy);
   /* It is hard to get the timing right for the polling engine poll. */
-  if (poller != nullptr && (0 == strcmp(poller, "poll"))) {
-    gpr_free(poller);
+  if ((0 == strcmp(poller.get(), "poll"))) {
     return;
   }
-  gpr_free(poller);
+#endif  // GRPC_POSIX_SOCKET
   const int kPingIntervalMS = 100;
   grpc_arg keepalive_arg_elems[3];
   keepalive_arg_elems[0].type = GRPC_ARG_INTEGER;
diff --git a/test/core/end2end/tests/retry_throttled.cc b/test/core/end2end/tests/retry_throttled.cc
index f5b28de..0e286c3 100644
--- a/test/core/end2end/tests/retry_throttled.cc
+++ b/test/core/end2end/tests/retry_throttled.cc
@@ -141,7 +141,7 @@
       // purposes of this test.)
       "  \"retryThrottling\": {\n"
       "    \"maxTokens\": 2,\n"
-      "    \"tokenRatio\": 1.0,\n"
+      "    \"tokenRatio\": 1.0\n"
       "  }\n"
       "}");
   grpc_channel_args client_args = {1, &arg};
diff --git a/test/core/end2end/tests/simple_request.cc b/test/core/end2end/tests/simple_request.cc
index 9c01896..a8c5789 100644
--- a/test/core/end2end/tests/simple_request.cc
+++ b/test/core/end2end/tests/simple_request.cc
@@ -86,6 +86,14 @@
   grpc_completion_queue_destroy(f->shutdown_cq);
 }
 
+static void check_peer(char* peer_name) {
+  // If the peer name is a uds path, then check if it is filled
+  if (strncmp(peer_name, "unix:/", strlen("unix:/")) == 0) {
+    GPR_ASSERT(strncmp(peer_name, "unix:/tmp/grpc_fullstack_test.",
+                       strlen("unix:/tmp/grpc_fullstack_test.")) == 0);
+  }
+}
+
 static void simple_request_body(grpc_end2end_test_config config,
                                 grpc_end2end_test_fixture f) {
   grpc_call* c;
@@ -166,10 +174,12 @@
   peer = grpc_call_get_peer(s);
   GPR_ASSERT(peer != nullptr);
   gpr_log(GPR_DEBUG, "server_peer=%s", peer);
+  check_peer(peer);
   gpr_free(peer);
   peer = grpc_call_get_peer(c);
   GPR_ASSERT(peer != nullptr);
   gpr_log(GPR_DEBUG, "client_peer=%s", peer);
+  check_peer(peer);
   gpr_free(peer);
 
   memset(ops, 0, sizeof(ops));
diff --git a/test/core/end2end/tests/stream_compression_compressed_payload.cc b/test/core/end2end/tests/stream_compression_compressed_payload.cc
index 839f091..39f95b8 100644
--- a/test/core/end2end/tests/stream_compression_compressed_payload.cc
+++ b/test/core/end2end/tests/stream_compression_compressed_payload.cc
@@ -30,6 +30,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/surface/call.h"
 #include "src/core/lib/surface/call_test_only.h"
 #include "src/core/lib/transport/static_metadata.h"
diff --git a/test/core/end2end/tests/stream_compression_payload.cc b/test/core/end2end/tests/stream_compression_payload.cc
index 4c08150..5f6b9a7 100644
--- a/test/core/end2end/tests/stream_compression_payload.cc
+++ b/test/core/end2end/tests/stream_compression_payload.cc
@@ -27,6 +27,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/surface/call.h"
 #include "test/core/end2end/cq_verifier.h"
 
diff --git a/test/core/end2end/tests/stream_compression_ping_pong_streaming.cc b/test/core/end2end/tests/stream_compression_ping_pong_streaming.cc
index f7af59f..6e96f7d 100644
--- a/test/core/end2end/tests/stream_compression_ping_pong_streaming.cc
+++ b/test/core/end2end/tests/stream_compression_ping_pong_streaming.cc
@@ -28,6 +28,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/surface/call.h"
 #include "test/core/end2end/cq_verifier.h"
 
diff --git a/test/core/end2end/tests/workaround_cronet_compression.cc b/test/core/end2end/tests/workaround_cronet_compression.cc
index f44ddca..d79b2a9 100644
--- a/test/core/end2end/tests/workaround_cronet_compression.cc
+++ b/test/core/end2end/tests/workaround_cronet_compression.cc
@@ -30,6 +30,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/compression/compression_args.h"
 #include "src/core/lib/surface/call.h"
 #include "src/core/lib/surface/call_test_only.h"
 #include "src/core/lib/transport/static_metadata.h"
diff --git a/test/core/gpr/arena_test.cc b/test/core/gpr/arena_test.cc
index de4bd98..1df1052 100644
--- a/test/core/gpr/arena_test.cc
+++ b/test/core/gpr/arena_test.cc
@@ -16,7 +16,7 @@
  *
  */
 
-#include "src/core/lib/gpr/arena.h"
+#include "src/core/lib/gprpp/arena.h"
 
 #include <inttypes.h>
 #include <string.h>
@@ -31,7 +31,9 @@
 #include "src/core/lib/gprpp/thd.h"
 #include "test/core/util/test_config.h"
 
-static void test_noop(void) { gpr_arena_destroy(gpr_arena_create(1)); }
+using grpc_core::Arena;
+
+static void test_noop(void) { Arena::Create(1)->Destroy(); }
 
 static void test(const char* name, size_t init_size, const size_t* allocs,
                  size_t nallocs) {
@@ -50,10 +52,10 @@
   gpr_log(GPR_INFO, "%s", s);
   gpr_free(s);
 
-  gpr_arena* a = gpr_arena_create(init_size);
+  Arena* a = Arena::Create(init_size);
   void** ps = static_cast<void**>(gpr_zalloc(sizeof(*ps) * nallocs));
   for (size_t i = 0; i < nallocs; i++) {
-    ps[i] = gpr_arena_alloc(a, allocs[i]);
+    ps[i] = a->Alloc(allocs[i]);
     // ensure the returned address is aligned
     GPR_ASSERT(((intptr_t)ps[i] & 0xf) == 0);
     // ensure no duplicate results
@@ -63,7 +65,7 @@
     // ensure writable
     memset(ps[i], 1, allocs[i]);
   }
-  gpr_arena_destroy(a);
+  a->Destroy();
   gpr_free(ps);
 }
 
@@ -80,14 +82,14 @@
 
 typedef struct {
   gpr_event ev_start;
-  gpr_arena* arena;
+  Arena* arena;
 } concurrent_test_args;
 
 static void concurrent_test_body(void* arg) {
   concurrent_test_args* a = static_cast<concurrent_test_args*>(arg);
   gpr_event_wait(&a->ev_start, gpr_inf_future(GPR_CLOCK_REALTIME));
   for (size_t i = 0; i < concurrent_test_iterations(); i++) {
-    *static_cast<char*>(gpr_arena_alloc(a->arena, 1)) = static_cast<char>(i);
+    *static_cast<char*>(a->arena->Alloc(1)) = static_cast<char>(i);
   }
 }
 
@@ -96,7 +98,7 @@
 
   concurrent_test_args args;
   gpr_event_init(&args.ev_start);
-  args.arena = gpr_arena_create(1024);
+  args.arena = Arena::Create(1024);
 
   grpc_core::Thread thds[CONCURRENT_TEST_THREADS];
 
@@ -112,7 +114,7 @@
     th.Join();
   }
 
-  gpr_arena_destroy(args.arena);
+  args.arena->Destroy();
 }
 
 int main(int argc, char* argv[]) {
diff --git a/test/core/gpr/env_test.cc b/test/core/gpr/env_test.cc
index a8206bd..3883a5d 100644
--- a/test/core/gpr/env_test.cc
+++ b/test/core/gpr/env_test.cc
@@ -42,8 +42,22 @@
   gpr_free(retrieved_value);
 }
 
+static void test_unsetenv(void) {
+  const char* name = "FOO";
+  const char* value = "BAR";
+  char* retrieved_value;
+
+  LOG_TEST_NAME("test_unsetenv");
+
+  gpr_setenv(name, value);
+  gpr_unsetenv(name);
+  retrieved_value = gpr_getenv(name);
+  GPR_ASSERT(retrieved_value == nullptr);
+}
+
 int main(int argc, char** argv) {
   grpc::testing::TestEnvironment env(argc, argv);
   test_setenv_getenv();
+  test_unsetenv();
   return 0;
 }
diff --git a/test/core/gpr/log_test.cc b/test/core/gpr/log_test.cc
index f962577..e320daa 100644
--- a/test/core/gpr/log_test.cc
+++ b/test/core/gpr/log_test.cc
@@ -21,9 +21,14 @@
 #include <stdbool.h>
 #include <string.h>
 
-#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gprpp/global_config.h"
 #include "test/core/util/test_config.h"
 
+// Config declaration is supposed to be located at log.h but
+// log.h doesn't include global_config headers because it has to
+// be a strict C so declaration statement gets to be here.
+GPR_GLOBAL_CONFIG_DECLARE_STRING(grpc_verbosity);
+
 static bool log_func_reached = false;
 
 static void test_callback(gpr_log_func_args* args) {
@@ -67,7 +72,7 @@
 
   /* gpr_log_verbosity_init() will be effective only once, and only before
    * gpr_set_log_verbosity() is called */
-  gpr_setenv("GRPC_VERBOSITY", "ERROR");
+  GPR_GLOBAL_CONFIG_SET(grpc_verbosity, "ERROR");
   gpr_log_verbosity_init();
 
   test_log_function_reached(GPR_ERROR);
@@ -75,7 +80,7 @@
   test_log_function_unreached(GPR_DEBUG);
 
   /* gpr_log_verbosity_init() should not be effective */
-  gpr_setenv("GRPC_VERBOSITY", "DEBUG");
+  GPR_GLOBAL_CONFIG_SET(grpc_verbosity, "DEBUG");
   gpr_log_verbosity_init();
   test_log_function_reached(GPR_ERROR);
   test_log_function_unreached(GPR_INFO);
@@ -97,7 +102,7 @@
   test_log_function_unreached(GPR_DEBUG);
 
   /* gpr_log_verbosity_init() should not be effective */
-  gpr_setenv("GRPC_VERBOSITY", "DEBUG");
+  GPR_GLOBAL_CONFIG_SET(grpc_verbosity, "DEBUG");
   gpr_log_verbosity_init();
   test_log_function_reached(GPR_ERROR);
   test_log_function_unreached(GPR_INFO);
diff --git a/test/core/gpr/string_test.cc b/test/core/gpr/string_test.cc
index 7da7b18..5e3ed9d 100644
--- a/test/core/gpr/string_test.cc
+++ b/test/core/gpr/string_test.cc
@@ -279,19 +279,20 @@
   GPR_ASSERT(0 == strcmp((const char*)gpr_memrchr("hello", 'l', 5), "lo"));
 }
 
-static void test_is_true(void) {
-  LOG_TEST_NAME("test_is_true");
+static void test_parse_bool_value(void) {
+  LOG_TEST_NAME("test_parse_bool_value");
 
-  GPR_ASSERT(true == gpr_is_true("True"));
-  GPR_ASSERT(true == gpr_is_true("true"));
-  GPR_ASSERT(true == gpr_is_true("TRUE"));
-  GPR_ASSERT(true == gpr_is_true("Yes"));
-  GPR_ASSERT(true == gpr_is_true("yes"));
-  GPR_ASSERT(true == gpr_is_true("YES"));
-  GPR_ASSERT(true == gpr_is_true("1"));
-  GPR_ASSERT(false == gpr_is_true(nullptr));
-  GPR_ASSERT(false == gpr_is_true(""));
-  GPR_ASSERT(false == gpr_is_true("0"));
+  bool ret;
+  GPR_ASSERT(true == gpr_parse_bool_value("truE", &ret) && true == ret);
+  GPR_ASSERT(true == gpr_parse_bool_value("falsE", &ret) && false == ret);
+  GPR_ASSERT(true == gpr_parse_bool_value("1", &ret) && true == ret);
+  GPR_ASSERT(true == gpr_parse_bool_value("0", &ret) && false == ret);
+  GPR_ASSERT(true == gpr_parse_bool_value("Yes", &ret) && true == ret);
+  GPR_ASSERT(true == gpr_parse_bool_value("No", &ret) && false == ret);
+  GPR_ASSERT(true == gpr_parse_bool_value("Y", &ret) && true == ret);
+  GPR_ASSERT(true == gpr_parse_bool_value("N", &ret) && false == ret);
+  GPR_ASSERT(false == gpr_parse_bool_value(nullptr, &ret));
+  GPR_ASSERT(false == gpr_parse_bool_value("", &ret));
 }
 
 int main(int argc, char** argv) {
@@ -307,6 +308,6 @@
   test_leftpad();
   test_stricmp();
   test_memrchr();
-  test_is_true();
+  test_parse_bool_value();
   return 0;
 }
diff --git a/test/core/gprpp/BUILD b/test/core/gprpp/BUILD
index c8d47be..cd3232a 100644
--- a/test/core/gprpp/BUILD
+++ b/test/core/gprpp/BUILD
@@ -29,6 +29,32 @@
 )
 
 grpc_cc_test(
+    name = "global_config_test",
+    srcs = ["global_config_test.cc"],
+    external_deps = [
+        "gtest",
+    ],
+    language = "C++",
+    deps = [
+        "//:gpr",
+        "//test/core/util:grpc_test_util",
+    ],
+)
+
+grpc_cc_test(
+    name = "global_config_env_test",
+    srcs = ["global_config_env_test.cc"],
+    external_deps = [
+        "gtest",
+    ],
+    language = "C++",
+    deps = [
+        "//:gpr",
+        "//test/core/util:grpc_test_util",
+    ],
+)
+
+grpc_cc_test(
     name = "manual_constructor_test",
     srcs = ["manual_constructor_test.cc"],
     language = "C++",
@@ -39,6 +65,19 @@
 )
 
 grpc_cc_test(
+    name = "grpc_core_map_test",
+    srcs = ["map_test.cc"],
+    external_deps = [
+        "gtest",
+    ],
+    language = "C++",
+    deps = [
+        "//:gpr_base",
+        "//test/core/util:grpc_test_util",
+    ],
+)
+
+grpc_cc_test(
     name = "memory_test",
     srcs = ["memory_test.cc"],
     external_deps = [
diff --git a/test/core/gprpp/global_config_env_test.cc b/test/core/gprpp/global_config_env_test.cc
new file mode 100644
index 0000000..74905d3
--- /dev/null
+++ b/test/core/gprpp/global_config_env_test.cc
@@ -0,0 +1,130 @@
+/*
+ *
+ * Copyright 2019 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 <stdio.h>
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gprpp/global_config_env.h"
+#include "src/core/lib/gprpp/memory.h"
+
+namespace {
+
+bool g_config_error_function_called;
+
+void ClearConfigErrorCalled() { g_config_error_function_called = false; }
+
+bool IsConfigErrorCalled() { return g_config_error_function_called; }
+
+// This function is for preventing the program from invoking
+// an error handler due to configuration error and
+// make test routines know whether there is error.
+void FakeConfigErrorFunction(const char* error_message) {
+  g_config_error_function_called = true;
+}
+
+class GlobalConfigEnvTest : public ::testing::Test {
+ protected:
+  void SetUp() override { ClearConfigErrorCalled(); }
+  void TearDown() override { EXPECT_FALSE(IsConfigErrorCalled()); }
+};
+
+}  // namespace
+
+GPR_GLOBAL_CONFIG_DEFINE_BOOL(bool_var, true, "");
+GPR_GLOBAL_CONFIG_DEFINE_INT32(int32_var, 1234, "");
+GPR_GLOBAL_CONFIG_DEFINE_STRING(string_var, "Apple", "");
+
+TEST_F(GlobalConfigEnvTest, BoolWithEnvTest) {
+  const char* bool_var_name = "BOOL_VAR";
+
+  gpr_unsetenv(bool_var_name);
+  EXPECT_TRUE(GPR_GLOBAL_CONFIG_GET(bool_var));
+
+  gpr_setenv(bool_var_name, "true");
+  EXPECT_TRUE(GPR_GLOBAL_CONFIG_GET(bool_var));
+
+  gpr_setenv(bool_var_name, "false");
+  EXPECT_FALSE(GPR_GLOBAL_CONFIG_GET(bool_var));
+
+  EXPECT_FALSE(IsConfigErrorCalled());
+
+  gpr_setenv(bool_var_name, "");
+  GPR_GLOBAL_CONFIG_GET(bool_var);
+  EXPECT_TRUE(IsConfigErrorCalled());
+  ClearConfigErrorCalled();
+
+  gpr_setenv(bool_var_name, "!");
+  GPR_GLOBAL_CONFIG_GET(bool_var);
+  EXPECT_TRUE(IsConfigErrorCalled());
+  ClearConfigErrorCalled();
+}
+
+TEST_F(GlobalConfigEnvTest, Int32WithEnvTest) {
+  const char* int32_var_name = "INT32_VAR";
+
+  gpr_unsetenv(int32_var_name);
+  EXPECT_EQ(1234, GPR_GLOBAL_CONFIG_GET(int32_var));
+
+  gpr_setenv(int32_var_name, "0");
+  EXPECT_EQ(0, GPR_GLOBAL_CONFIG_GET(int32_var));
+
+  gpr_setenv(int32_var_name, "-123456789");
+  EXPECT_EQ(-123456789, GPR_GLOBAL_CONFIG_GET(int32_var));
+
+  gpr_setenv(int32_var_name, "123456789");
+  EXPECT_EQ(123456789, GPR_GLOBAL_CONFIG_GET(int32_var));
+
+  EXPECT_FALSE(IsConfigErrorCalled());
+
+  gpr_setenv(int32_var_name, "-1AB");
+  GPR_GLOBAL_CONFIG_GET(int32_var);
+  EXPECT_TRUE(IsConfigErrorCalled());
+  ClearConfigErrorCalled();
+}
+
+TEST_F(GlobalConfigEnvTest, StringWithEnvTest) {
+  const char* string_var_name = "STRING_VAR";
+  grpc_core::UniquePtr<char> value;
+
+  gpr_unsetenv(string_var_name);
+  value = GPR_GLOBAL_CONFIG_GET(string_var);
+  EXPECT_EQ(0, strcmp(value.get(), "Apple"));
+
+  gpr_setenv(string_var_name, "Banana");
+  value = GPR_GLOBAL_CONFIG_GET(string_var);
+  EXPECT_EQ(0, strcmp(value.get(), "Banana"));
+
+  gpr_setenv(string_var_name, "");
+  value = GPR_GLOBAL_CONFIG_GET(string_var);
+  EXPECT_EQ(0, strcmp(value.get(), ""));
+}
+
+int main(int argc, char** argv) {
+  // Not to abort the test when parsing error happens.
+  grpc_core::SetGlobalConfigEnvErrorFunction(&FakeConfigErrorFunction);
+
+  ::testing::InitGoogleTest(&argc, argv);
+  int ret = RUN_ALL_TESTS();
+  return ret;
+}
diff --git a/test/core/gprpp/global_config_test.cc b/test/core/gprpp/global_config_test.cc
new file mode 100644
index 0000000..7da78b6
--- /dev/null
+++ b/test/core/gprpp/global_config_test.cc
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright 2019 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 <stdio.h>
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gprpp/global_config.h"
+#include "src/core/lib/gprpp/memory.h"
+
+GPR_GLOBAL_CONFIG_DECLARE_BOOL(bool_var);
+
+GPR_GLOBAL_CONFIG_DEFINE_BOOL(bool_var, false, "");
+GPR_GLOBAL_CONFIG_DEFINE_INT32(int32_var, 0, "");
+GPR_GLOBAL_CONFIG_DEFINE_STRING(string_var, "", "");
+
+TEST(GlobalConfigTest, BoolTest) {
+  EXPECT_FALSE(GPR_GLOBAL_CONFIG_GET(bool_var));
+  GPR_GLOBAL_CONFIG_SET(bool_var, true);
+  EXPECT_TRUE(GPR_GLOBAL_CONFIG_GET(bool_var));
+}
+
+TEST(GlobalConfigTest, Int32Test) {
+  EXPECT_EQ(0, GPR_GLOBAL_CONFIG_GET(int32_var));
+  GPR_GLOBAL_CONFIG_SET(int32_var, 1024);
+  EXPECT_EQ(1024, GPR_GLOBAL_CONFIG_GET(int32_var));
+}
+
+TEST(GlobalConfigTest, StringTest) {
+  grpc_core::UniquePtr<char> value;
+
+  value = GPR_GLOBAL_CONFIG_GET(string_var);
+  EXPECT_EQ(0, strcmp(value.get(), ""));
+
+  GPR_GLOBAL_CONFIG_SET(string_var, "Test");
+
+  value = GPR_GLOBAL_CONFIG_GET(string_var);
+  EXPECT_EQ(0, strcmp(value.get(), "Test"));
+}
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  int ret = RUN_ALL_TESTS();
+  return ret;
+}
diff --git a/test/core/gprpp/map_test.cc b/test/core/gprpp/map_test.cc
new file mode 100644
index 0000000..e70bf38
--- /dev/null
+++ b/test/core/gprpp/map_test.cc
@@ -0,0 +1,409 @@
+/*
+ *
+ * Copyright 2017 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/lib/gprpp/map.h"
+#include <gtest/gtest.h>
+#include "include/grpc/support/string_util.h"
+#include "src/core/lib/gprpp/inlined_vector.h"
+#include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/gprpp/orphanable.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "test/core/util/test_config.h"
+
+namespace grpc_core {
+namespace testing {
+class Payload {
+ public:
+  Payload() : data_(-1) {}
+  explicit Payload(int data) : data_(data) {}
+  Payload(const Payload& other) : data_(other.data_) {}
+  Payload& operator=(const Payload& other) {
+    if (this != &other) {
+      data_ = other.data_;
+    }
+    return *this;
+  }
+  int data() { return data_; }
+
+ private:
+  int data_;
+};
+
+inline UniquePtr<char> CopyString(const char* string) {
+  return UniquePtr<char>(gpr_strdup(string));
+}
+
+static constexpr char kKeys[][4] = {"abc", "efg", "hij", "klm", "xyz"};
+
+class MapTest : public ::testing::Test {
+ public:
+  template <class Key, class T, class Compare>
+  typename ::grpc_core::Map<Key, T, Compare>::Entry* Root(
+      typename ::grpc_core::Map<Key, T, Compare>* map) {
+    return map->root_;
+  }
+};
+
+// Test insertion of Payload
+TEST_F(MapTest, EmplaceAndFind) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], Payload(i));
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map.find(kKeys[i])->second.data());
+  }
+}
+
+// Test insertion of Payload Unique Ptrs
+TEST_F(MapTest, EmplaceAndFindWithUniquePtrValue) {
+  Map<const char*, UniquePtr<Payload>, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], MakeUnique<Payload>(i));
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map.find(kKeys[i])->second->data());
+  }
+}
+
+// Test insertion of Unique Ptr kKeys and Payload
+TEST_F(MapTest, EmplaceAndFindWithUniquePtrKey) {
+  Map<UniquePtr<char>, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(CopyString(kKeys[i]), Payload(i));
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map.find(CopyString(kKeys[i]))->second.data());
+  }
+}
+
+// Test insertion of Payload
+TEST_F(MapTest, InsertAndFind) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.insert(MakePair(kKeys[i], Payload(i)));
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map.find(kKeys[i])->second.data());
+  }
+}
+
+// Test insertion of Payload Unique Ptrs
+TEST_F(MapTest, InsertAndFindWithUniquePtrValue) {
+  Map<const char*, UniquePtr<Payload>, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.insert(MakePair(kKeys[i], MakeUnique<Payload>(i)));
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map.find(kKeys[i])->second->data());
+  }
+}
+
+// Test insertion of Unique Ptr kKeys and Payload
+TEST_F(MapTest, InsertAndFindWithUniquePtrKey) {
+  Map<UniquePtr<char>, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.insert(MakePair(CopyString(kKeys[i]), Payload(i)));
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map.find(CopyString(kKeys[i]))->second.data());
+  }
+}
+
+// Test bracket operators
+TEST_F(MapTest, BracketOperator) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map[kKeys[i]] = Payload(i);
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map[kKeys[i]].data());
+  }
+}
+
+// Test bracket operators with unique pointer to payload
+TEST_F(MapTest, BracketOperatorWithUniquePtrValue) {
+  Map<const char*, UniquePtr<Payload>, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map[kKeys[i]] = MakeUnique<Payload>(i);
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map[kKeys[i]]->data());
+  }
+}
+
+// Test bracket operators with unique pointer to payload
+TEST_F(MapTest, BracketOperatorWithUniquePtrKey) {
+  Map<UniquePtr<char>, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map[CopyString(kKeys[i])] = Payload(i);
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map[CopyString(kKeys[i])].data());
+  }
+}
+
+// Test removal of a single value
+TEST_F(MapTest, Erase) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], Payload(i));
+  }
+  EXPECT_EQ(test_map.size(), 5UL);
+  EXPECT_EQ(test_map.erase(kKeys[3]), 1UL);  // Remove "hij"
+  for (int i = 0; i < 5; i++) {
+    if (i == 3) {  // "hij" should not be present
+      EXPECT_TRUE(test_map.find(kKeys[i]) == test_map.end());
+    } else {
+      EXPECT_EQ(i, test_map.find(kKeys[i])->second.data());
+    }
+  }
+  EXPECT_EQ(test_map.size(), 4UL);
+}
+
+// Test removal of a single value with unique ptr to payload
+TEST_F(MapTest, EraseWithUniquePtrValue) {
+  Map<const char*, UniquePtr<Payload>, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], MakeUnique<Payload>(i));
+  }
+  EXPECT_EQ(test_map.size(), 5UL);
+  test_map.erase(kKeys[3]);  // Remove "hij"
+  for (int i = 0; i < 5; i++) {
+    if (i == 3) {  // "hij" should not be present
+      EXPECT_TRUE(test_map.find(kKeys[i]) == test_map.end());
+    } else {
+      EXPECT_EQ(i, test_map.find(kKeys[i])->second->data());
+    }
+  }
+  EXPECT_EQ(test_map.size(), 4UL);
+}
+
+// Test removal of a single value
+TEST_F(MapTest, EraseWithUniquePtrKey) {
+  Map<UniquePtr<char>, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(CopyString(kKeys[i]), Payload(i));
+  }
+  EXPECT_EQ(test_map.size(), 5UL);
+  test_map.erase(CopyString(kKeys[3]));  // Remove "hij"
+  for (int i = 0; i < 5; i++) {
+    if (i == 3) {  // "hij" should not be present
+      EXPECT_TRUE(test_map.find(CopyString(kKeys[i])) == test_map.end());
+    } else {
+      EXPECT_EQ(i, test_map.find(CopyString(kKeys[i]))->second.data());
+    }
+  }
+  EXPECT_EQ(test_map.size(), 4UL);
+}
+
+// Test clear
+TEST_F(MapTest, SizeAndClear) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], Payload(i));
+  }
+  EXPECT_EQ(test_map.size(), 5UL);
+  EXPECT_FALSE(test_map.empty());
+  test_map.clear();
+  EXPECT_EQ(test_map.size(), 0UL);
+  EXPECT_TRUE(test_map.empty());
+}
+
+// Test clear with unique ptr payload
+TEST_F(MapTest, SizeAndClearWithUniquePtrValue) {
+  Map<const char*, UniquePtr<Payload>, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], MakeUnique<Payload>(i));
+  }
+  EXPECT_EQ(test_map.size(), 5UL);
+  EXPECT_FALSE(test_map.empty());
+  test_map.clear();
+  EXPECT_EQ(test_map.size(), 0UL);
+  EXPECT_TRUE(test_map.empty());
+}
+
+// Test clear with unique ptr char key
+TEST_F(MapTest, SizeAndClearWithUniquePtrKey) {
+  Map<UniquePtr<char>, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(CopyString(kKeys[i]), Payload(i));
+  }
+  EXPECT_EQ(test_map.size(), 5UL);
+  EXPECT_FALSE(test_map.empty());
+  test_map.clear();
+  EXPECT_EQ(test_map.size(), 0UL);
+  EXPECT_TRUE(test_map.empty());
+}
+
+// Test correction of Left-Left Tree imbalance
+TEST_F(MapTest, MapLL) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 2; i >= 0; i--) {
+    test_map.emplace(kKeys[i], Payload(i));
+  }
+  EXPECT_EQ(strcmp(Root(&test_map)->pair.first, kKeys[1]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->left->pair.first, kKeys[0]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->right->pair.first, kKeys[2]), 0);
+}
+
+// Test correction of Left-Right tree imbalance
+TEST_F(MapTest, MapLR) {
+  Map<const char*, Payload, StringLess> test_map;
+  int insertion_key_index[] = {2, 0, 1};
+  for (int i = 0; i < 3; i++) {
+    int key_index = insertion_key_index[i];
+    test_map.emplace(kKeys[key_index], Payload(key_index));
+  }
+  EXPECT_EQ(strcmp(Root(&test_map)->pair.first, kKeys[1]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->left->pair.first, kKeys[0]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->right->pair.first, kKeys[2]), 0);
+}
+
+// Test correction of Right-Left tree imbalance
+TEST_F(MapTest, MapRL) {
+  Map<const char*, Payload, StringLess> test_map;
+  int insertion_key_index[] = {0, 2, 1};
+  for (int i = 0; i < 3; i++) {
+    int key_index = insertion_key_index[i];
+    test_map.emplace(kKeys[key_index], Payload(key_index));
+  }
+  EXPECT_EQ(strcmp(Root(&test_map)->pair.first, kKeys[1]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->left->pair.first, kKeys[0]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->right->pair.first, kKeys[2]), 0);
+}
+
+// Test correction of Right-Right tree imbalance
+TEST_F(MapTest, MapRR) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], Payload(i));
+  }
+  EXPECT_EQ(strcmp(Root(&test_map)->pair.first, kKeys[1]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->left->pair.first, kKeys[0]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->right->pair.first, kKeys[3]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->right->left->pair.first, kKeys[2]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->right->right->pair.first, kKeys[4]), 0);
+}
+
+// Test correction after random insertion
+TEST_F(MapTest, MapRandomInsertions) {
+  Map<const char*, Payload, StringLess> test_map;
+  int insertion_key_index[] = {1, 4, 3, 0, 2};
+  for (int i = 0; i < 5; i++) {
+    int key_index = insertion_key_index[i];
+    test_map.emplace(kKeys[key_index], Payload(key_index));
+  }
+  EXPECT_EQ(strcmp(Root(&test_map)->pair.first, kKeys[3]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->left->pair.first, kKeys[1]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->right->pair.first, kKeys[4]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->left->right->pair.first, kKeys[2]), 0);
+  EXPECT_EQ(strcmp(Root(&test_map)->left->left->pair.first, kKeys[0]), 0);
+}
+
+// Test Map iterator
+TEST_F(MapTest, Iteration) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], Payload(i));
+  }
+  int count = 0;
+  for (auto iter = test_map.begin(); iter != test_map.end(); iter++) {
+    EXPECT_EQ(iter->second.data(), count);
+    count++;
+  }
+  EXPECT_EQ(count, 5);
+}
+
+// Test Map iterator with unique ptr payload
+TEST_F(MapTest, IterationWithUniquePtrValue) {
+  Map<const char*, UniquePtr<Payload>, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], MakeUnique<Payload>(i));
+  }
+  int count = 0;
+  for (auto iter = test_map.begin(); iter != test_map.end(); iter++) {
+    EXPECT_EQ(iter->second->data(), count);
+    count++;
+  }
+  EXPECT_EQ(count, 5);
+}
+
+// Test Map iterator with unique ptr to char key
+TEST_F(MapTest, IterationWithUniquePtrKey) {
+  Map<UniquePtr<char>, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(CopyString(kKeys[i]), Payload(i));
+  }
+  int count = 0;
+  for (auto iter = test_map.begin(); iter != test_map.end(); iter++) {
+    EXPECT_EQ(iter->second.data(), count);
+    count++;
+  }
+  EXPECT_EQ(count, 5);
+}
+
+// Test removing entries while iterating the map
+TEST_F(MapTest, EraseUsingIterator) {
+  Map<const char*, Payload, StringLess> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(kKeys[i], Payload(i));
+  }
+  int count = 0;
+  for (auto iter = test_map.begin(); iter != test_map.end();) {
+    EXPECT_EQ(iter->second.data(), count);
+    iter = test_map.erase(iter);
+    count++;
+  }
+  EXPECT_EQ(count, 5);
+  EXPECT_TRUE(test_map.empty());
+}
+
+// Random ops on a Map with Integer key of Payload value,
+// tests default comparator
+TEST_F(MapTest, RandomOpsWithIntKey) {
+  Map<int, Payload> test_map;
+  for (int i = 0; i < 5; i++) {
+    test_map.emplace(i, Payload(i));
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i, test_map.find(i)->second.data());
+  }
+  for (int i = 0; i < 5; i++) {
+    test_map[i] = Payload(i + 10);
+  }
+  for (int i = 0; i < 5; i++) {
+    EXPECT_EQ(i + 10, test_map[i].data());
+  }
+  EXPECT_EQ(test_map.erase(3), 1UL);
+  EXPECT_TRUE(test_map.find(3) == test_map.end());
+  EXPECT_FALSE(test_map.empty());
+  EXPECT_EQ(test_map.size(), 4UL);
+  test_map.clear();
+  EXPECT_EQ(test_map.size(), 0UL);
+  EXPECT_TRUE(test_map.empty());
+}
+
+}  // namespace testing
+}  // namespace grpc_core
+
+int main(int argc, char** argv) {
+  grpc::testing::TestEnvironment env(argc, argv);
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/core/handshake/BUILD b/test/core/handshake/BUILD
index b9d2f31..0824efb 100644
--- a/test/core/handshake/BUILD
+++ b/test/core/handshake/BUILD
@@ -32,6 +32,7 @@
         "//:grpc",
         "//test/core/util:grpc_test_util",
     ],
+    tags = ["no_windows"],
 )
 
 grpc_cc_library(
@@ -43,6 +44,7 @@
         "//:grpc",
         "//test/core/util:grpc_test_util",
     ],
+    tags = ["no_windows"],
 )
 
 grpc_cc_test(
@@ -60,6 +62,7 @@
         "//:grpc",
         "//test/core/util:grpc_test_util",
     ],
+    tags = ["no_windows"],
 )
 
 grpc_cc_test(
@@ -77,6 +80,7 @@
         "//:grpc",
         "//test/core/util:grpc_test_util",
     ],
+    tags = ["no_windows"],
 )
 
 grpc_cc_test(
diff --git a/test/core/http/httpscli_test.cc b/test/core/http/httpscli_test.cc
index 326b0e9..e7250c2 100644
--- a/test/core/http/httpscli_test.cc
+++ b/test/core/http/httpscli_test.cc
@@ -29,6 +29,7 @@
 
 #include "src/core/lib/gpr/env.h"
 #include "src/core/lib/iomgr/iomgr.h"
+#include "src/core/lib/security/security_connector/ssl_utils.h"
 #include "test/core/util/port.h"
 #include "test/core/util/subprocess.h"
 #include "test/core/util/test_config.h"
@@ -184,7 +185,7 @@
   /* Set the environment variable for the SSL certificate file */
   char* pem_file;
   gpr_asprintf(&pem_file, "%s/src/core/tsi/test_creds/ca.pem", root);
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, pem_file);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, pem_file);
   gpr_free(pem_file);
 
   /* start the server */
diff --git a/test/core/iomgr/resolve_address_posix_test.cc b/test/core/iomgr/resolve_address_posix_test.cc
index 826c7e1..112d7c2 100644
--- a/test/core/iomgr/resolve_address_posix_test.cc
+++ b/test/core/iomgr/resolve_address_posix_test.cc
@@ -29,6 +29,7 @@
 #include <grpc/support/sync.h>
 #include <grpc/support/time.h>
 
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
 #include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/useful.h"
@@ -224,15 +225,16 @@
   // --resolver will always be the first one, so only parse the first argument
   // (other arguments may be unknown to cl)
   gpr_cmdline_parse(cl, argc > 2 ? 2 : argc, argv);
-  const char* cur_resolver = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (cur_resolver != nullptr && strlen(cur_resolver) != 0) {
+  grpc_core::UniquePtr<char> resolver =
+      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+  if (strlen(resolver.get()) != 0) {
     gpr_log(GPR_INFO, "Warning: overriding resolver setting of %s",
-            cur_resolver);
+            resolver.get());
   }
   if (gpr_stricmp(resolver_type, "native") == 0) {
-    gpr_setenv("GRPC_DNS_RESOLVER", "native");
+    GPR_GLOBAL_CONFIG_SET(grpc_dns_resolver, "native");
   } else if (gpr_stricmp(resolver_type, "ares") == 0) {
-    gpr_setenv("GRPC_DNS_RESOLVER", "ares");
+    GPR_GLOBAL_CONFIG_SET(grpc_dns_resolver, "ares");
   } else {
     gpr_log(GPR_ERROR, "--resolver_type was not set to ares or native");
     abort();
@@ -246,12 +248,12 @@
     // c-ares resolver doesn't support UDS (ability for native DNS resolver
     // to handle this is only expected to be used by servers, which
     // unconditionally use the native DNS resolver).
-    char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
-    if (resolver_env == nullptr || gpr_stricmp(resolver_env, "native") == 0) {
+    grpc_core::UniquePtr<char> resolver =
+        GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+    if (gpr_stricmp(resolver.get(), "native") == 0) {
       test_unix_socket();
       test_unix_socket_path_name_too_long();
     }
-    gpr_free(resolver_env);
   }
   gpr_cmdline_destroy(cl);
 
diff --git a/test/core/iomgr/resolve_address_test.cc b/test/core/iomgr/resolve_address_test.cc
index f59a992..cbc0348 100644
--- a/test/core/iomgr/resolve_address_test.cc
+++ b/test/core/iomgr/resolve_address_test.cc
@@ -27,7 +27,7 @@
 
 #include <string.h>
 
-#include "src/core/lib/gpr/env.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/iomgr/executor.h"
 #include "src/core/lib/iomgr/iomgr.h"
@@ -83,7 +83,9 @@
 
 static void poll_pollset_until_request_done(args_struct* args) {
   grpc_core::ExecCtx exec_ctx;
-  grpc_millis deadline = n_sec_deadline(10);
+  // Try to give enough time for c-ares to run through its retries
+  // a few times if needed.
+  grpc_millis deadline = n_sec_deadline(90);
   while (true) {
     bool done = gpr_atm_acq_load(&args->done_atm) != 0;
     if (done) {
@@ -345,16 +347,17 @@
   // --resolver will always be the first one, so only parse the first argument
   // (other arguments may be unknown to cl)
   gpr_cmdline_parse(cl, argc > 2 ? 2 : argc, argv);
-  const char* cur_resolver = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (cur_resolver != nullptr && strlen(cur_resolver) != 0) {
+  grpc_core::UniquePtr<char> resolver =
+      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+  if (strlen(resolver.get()) != 0) {
     gpr_log(GPR_INFO, "Warning: overriding resolver setting of %s",
-            cur_resolver);
+            resolver.get());
   }
   if (gpr_stricmp(resolver_type, "native") == 0) {
-    gpr_setenv("GRPC_DNS_RESOLVER", "native");
+    GPR_GLOBAL_CONFIG_SET(grpc_dns_resolver, "native");
   } else if (gpr_stricmp(resolver_type, "ares") == 0) {
 #ifndef GRPC_UV
-    gpr_setenv("GRPC_DNS_RESOLVER", "ares");
+    GPR_GLOBAL_CONFIG_SET(grpc_dns_resolver, "ares");
 #endif
   } else {
     gpr_log(GPR_ERROR, "--resolver_type was not set to ares or native");
@@ -371,15 +374,10 @@
     test_missing_default_port();
     test_ipv6_with_port();
     test_ipv6_without_port();
-    if (gpr_stricmp(resolver_type, "ares") != 0) {
-      // These tests can trigger DNS queries to the nearby nameserver
-      // that need to come back in order for the test to succeed.
-      // c-ares is prone to not using the local system caches that the
-      // native getaddrinfo implementations take advantage of, so running
-      // these unit tests under c-ares risks flakiness.
-      test_invalid_ip_addresses();
-      test_unparseable_hostports();
-    } else {
+    test_invalid_ip_addresses();
+    test_unparseable_hostports();
+    if (gpr_stricmp(resolver_type, "ares") == 0) {
+      // This behavior expectation is specific to c-ares.
       test_localhost_result_has_ipv6_first();
     }
     grpc_core::Executor::ShutdownAll();
diff --git a/test/core/iomgr/socket_utils_test.cc b/test/core/iomgr/socket_utils_test.cc
index 4208737..f0537e0 100644
--- a/test/core/iomgr/socket_utils_test.cc
+++ b/test/core/iomgr/socket_utils_test.cc
@@ -24,6 +24,7 @@
 #include "src/core/lib/iomgr/socket_utils_posix.h"
 
 #include <errno.h>
+#include <netinet/in.h>
 #include <netinet/ip.h>
 #include <string.h>
 
diff --git a/test/core/network_benchmarks/BUILD b/test/core/network_benchmarks/BUILD
index fbc611d..1194e4a 100644
--- a/test/core/network_benchmarks/BUILD
+++ b/test/core/network_benchmarks/BUILD
@@ -33,4 +33,5 @@
         "//:grpc",
         "//test/core/util:grpc_test_util",
     ],
+    tags = ["no_windows"],
 )
diff --git a/test/core/security/credentials_test.cc b/test/core/security/credentials_test.cc
index 11cfc8c..141346b 100644
--- a/test/core/security/credentials_test.cc
+++ b/test/core/security/credentials_test.cc
@@ -1161,7 +1161,7 @@
   GPR_ASSERT(path != nullptr);
   gpr_free(path);
 #if defined(GPR_POSIX_ENV) || defined(GPR_LINUX_ENV)
-  unsetenv("HOME");
+  gpr_unsetenv("HOME");
   path = grpc_get_well_known_google_credentials_file_path();
   GPR_ASSERT(path == nullptr);
   gpr_setenv("HOME", home);
diff --git a/test/core/security/security_connector_test.cc b/test/core/security/security_connector_test.cc
index 496f064..c888c90 100644
--- a/test/core/security/security_connector_test.cc
+++ b/test/core/security/security_connector_test.cc
@@ -24,7 +24,6 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
@@ -394,7 +393,7 @@
 
   /* First let's get the root through the override: set the env to an invalid
      value. */
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, "");
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, "");
   grpc_set_ssl_roots_override_callback(override_roots_success);
   grpc_slice roots =
       grpc_core::TestDefaultSslRootStore::ComputePemRootCertsForTesting();
@@ -405,7 +404,8 @@
 
   /* Now let's set the env var: We should get the contents pointed value
      instead. */
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_env_var_file_path);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path,
+                        roots_env_var_file_path);
   roots = grpc_core::TestDefaultSslRootStore::ComputePemRootCertsForTesting();
   roots_contents = grpc_slice_to_c_string(roots);
   grpc_slice_unref(roots);
@@ -414,7 +414,7 @@
 
   /* Now reset the env var. We should fall back to the value overridden using
      the api. */
-  gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, "");
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, "");
   roots = grpc_core::TestDefaultSslRootStore::ComputePemRootCertsForTesting();
   roots_contents = grpc_slice_to_c_string(roots);
   grpc_slice_unref(roots);
@@ -423,7 +423,7 @@
 
   /* Now setup a permanent failure for the overridden roots and we should get
      an empty slice. */
-  gpr_setenv("GRPC_NOT_USE_SYSTEM_SSL_ROOTS", "true");
+  GPR_GLOBAL_CONFIG_SET(grpc_not_use_system_ssl_roots, true);
   grpc_set_ssl_roots_override_callback(override_roots_permanent_failure);
   roots = grpc_core::TestDefaultSslRootStore::ComputePemRootCertsForTesting();
   GPR_ASSERT(GRPC_SLICE_IS_EMPTY(roots));
diff --git a/test/core/slice/slice_test.cc b/test/core/slice/slice_test.cc
index 1e53a19..6ed0236 100644
--- a/test/core/slice/slice_test.cc
+++ b/test/core/slice/slice_test.cc
@@ -51,13 +51,6 @@
     }
     /* Returned slice length must be what was requested. */
     GPR_ASSERT(GRPC_SLICE_LENGTH(slice) == length);
-    /* If the slice has a refcount, it must be destroyable. */
-    if (slice.refcount) {
-      GPR_ASSERT(slice.refcount->vtable != nullptr);
-      GPR_ASSERT(slice.refcount->vtable->ref != nullptr);
-      GPR_ASSERT(slice.refcount->vtable->unref != nullptr);
-      GPR_ASSERT(slice.refcount->vtable->hash != nullptr);
-    }
     /* We must be able to write to every byte of the data */
     for (i = 0; i < length; i++) {
       GRPC_SLICE_START_PTR(slice)[i] = static_cast<uint8_t>(i);
diff --git a/test/core/transport/connectivity_state_test.cc b/test/core/transport/connectivity_state_test.cc
index 7c7e308..26c09a7 100644
--- a/test/core/transport/connectivity_state_test.cc
+++ b/test/core/transport/connectivity_state_test.cc
@@ -60,13 +60,9 @@
 static void test_check(void) {
   grpc_connectivity_state_tracker tracker;
   grpc_core::ExecCtx exec_ctx;
-  grpc_error* error;
   gpr_log(GPR_DEBUG, "test_check");
   grpc_connectivity_state_init(&tracker, GRPC_CHANNEL_IDLE, "xxx");
-  GPR_ASSERT(grpc_connectivity_state_get(&tracker, &error) ==
-             GRPC_CHANNEL_IDLE);
   GPR_ASSERT(grpc_connectivity_state_check(&tracker) == GRPC_CHANNEL_IDLE);
-  GPR_ASSERT(error == GRPC_ERROR_NONE);
   grpc_connectivity_state_destroy(&tracker);
 }
 
diff --git a/test/core/transport/stream_owned_slice_test.cc b/test/core/transport/stream_owned_slice_test.cc
index 48a77db..c489b11 100644
--- a/test/core/transport/stream_owned_slice_test.cc
+++ b/test/core/transport/stream_owned_slice_test.cc
@@ -32,14 +32,11 @@
   uint8_t buffer[] = "abc123";
   grpc_stream_refcount r;
   GRPC_STREAM_REF_INIT(&r, 1, do_nothing, nullptr, "test");
-  GPR_ASSERT(r.refs.count == 1);
   grpc_slice slice =
       grpc_slice_from_stream_owned_buffer(&r, buffer, sizeof(buffer));
   GPR_ASSERT(GRPC_SLICE_START_PTR(slice) == buffer);
   GPR_ASSERT(GRPC_SLICE_LENGTH(slice) == sizeof(buffer));
-  GPR_ASSERT(r.refs.count == 2);
   grpc_slice_unref(slice);
-  GPR_ASSERT(r.refs.count == 1);
 
   grpc_shutdown();
   return 0;
diff --git a/test/core/util/BUILD b/test/core/util/BUILD
index b931a9d..47f814a 100644
--- a/test/core/util/BUILD
+++ b/test/core/util/BUILD
@@ -76,17 +76,17 @@
         "tracer_util.h",
         "trickle_endpoint.h",
     ],
+    data = [
+        "lsan_suppressions.txt",
+        "tsan_suppressions.txt",
+        "ubsan_suppressions.txt",
+    ],
     language = "C++",
     deps = [
         ":grpc_debugger_macros",
         "//:gpr",
         "//:grpc_common",
     ],
-    data = [
-        "lsan_suppressions.txt",
-        "tsan_suppressions.txt",
-        "ubsan_suppressions.txt",
-    ],
 )
 
 grpc_cc_library(
@@ -132,7 +132,9 @@
     deps = [
         ":grpc_test_util",
         "//:grpc",
+        "//test/cpp/util:test_config",
     ],
+    tags = ["no_windows"],
 )
 
 grpc_cc_test(
diff --git a/test/core/util/fuzzer_corpus_test.cc b/test/core/util/fuzzer_corpus_test.cc
index 6e3785c..864533c 100644
--- a/test/core/util/fuzzer_corpus_test.cc
+++ b/test/core/util/fuzzer_corpus_test.cc
@@ -29,6 +29,7 @@
 #include "src/core/lib/gpr/env.h"
 #include "src/core/lib/iomgr/load_file.h"
 #include "test/core/util/test_config.h"
+#include "test/cpp/util/test_config.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
 extern bool squelch;
@@ -145,8 +146,8 @@
 
 int main(int argc, char** argv) {
   grpc::testing::TestEnvironment env(argc, argv);
-  ParseCommandLineFlags(&argc, &argv, true);
   ::testing::InitGoogleTest(&argc, argv);
+  grpc::testing::InitTest(&argc, &argv, true);
 
   return RUN_ALL_TESTS();
 }
diff --git a/test/core/util/reconnect_server.cc b/test/core/util/reconnect_server.cc
index ad7cf42..144ad64 100644
--- a/test/core/util/reconnect_server.cc
+++ b/test/core/util/reconnect_server.cc
@@ -109,7 +109,7 @@
 }
 
 void reconnect_server_poll(reconnect_server* server, int seconds) {
-  test_tcp_server_poll(&server->tcp_server, seconds);
+  test_tcp_server_poll(&server->tcp_server, 1000 * seconds);
 }
 
 void reconnect_server_clear_timestamps(reconnect_server* server) {
diff --git a/test/core/util/test_config.cc b/test/core/util/test_config.cc
index 476e424..5b248a0 100644
--- a/test/core/util/test_config.cc
+++ b/test/core/util/test_config.cc
@@ -28,7 +28,6 @@
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/surface/init.h"
diff --git a/test/core/util/test_lb_policies.cc b/test/core/util/test_lb_policies.cc
index 05e25eb..b871f04 100644
--- a/test/core/util/test_lb_policies.cc
+++ b/test/core/util/test_lb_policies.cc
@@ -150,12 +150,11 @@
       return parent_->channel_control_helper()->CreateChannel(target, args);
     }
 
-    void UpdateState(grpc_connectivity_state state, grpc_error* state_error,
+    void UpdateState(grpc_connectivity_state state,
                      UniquePtr<SubchannelPicker> picker) override {
       parent_->channel_control_helper()->UpdateState(
-          state, state_error,
-          UniquePtr<SubchannelPicker>(
-              New<Picker>(std::move(picker), cb_, user_data_)));
+          state, UniquePtr<SubchannelPicker>(
+                     New<Picker>(std::move(picker), cb_, user_data_)));
     }
 
     void RequestReresolution() override {
diff --git a/test/core/util/test_tcp_server.cc b/test/core/util/test_tcp_server.cc
index 610a991..80d0634 100644
--- a/test/core/util/test_tcp_server.cc
+++ b/test/core/util/test_tcp_server.cc
@@ -77,11 +77,11 @@
   gpr_log(GPR_INFO, "test tcp server listening on 0.0.0.0:%d", port);
 }
 
-void test_tcp_server_poll(test_tcp_server* server, int seconds) {
+void test_tcp_server_poll(test_tcp_server* server, int milliseconds) {
   grpc_pollset_worker* worker = nullptr;
   grpc_core::ExecCtx exec_ctx;
   grpc_millis deadline = grpc_timespec_to_millis_round_up(
-      grpc_timeout_seconds_to_deadline(seconds));
+      grpc_timeout_milliseconds_to_deadline(milliseconds));
   gpr_mu_lock(server->mu);
   GRPC_LOG_IF_ERROR("pollset_work",
                     grpc_pollset_work(server->pollset, &worker, deadline));
@@ -104,7 +104,7 @@
                                    gpr_time_from_seconds(5, GPR_TIMESPAN));
   while (!server->shutdown &&
          gpr_time_cmp(gpr_now(GPR_CLOCK_MONOTONIC), shutdown_deadline) < 0) {
-    test_tcp_server_poll(server, 1);
+    test_tcp_server_poll(server, 1000);
   }
   grpc_pollset_shutdown(server->pollset,
                         GRPC_CLOSURE_CREATE(finish_pollset, server->pollset,
diff --git a/test/core/util/test_tcp_server.h b/test/core/util/test_tcp_server.h
index 5814038..313a27a 100644
--- a/test/core/util/test_tcp_server.h
+++ b/test/core/util/test_tcp_server.h
@@ -35,7 +35,7 @@
 void test_tcp_server_init(test_tcp_server* server,
                           grpc_tcp_server_cb on_connect, void* user_data);
 void test_tcp_server_start(test_tcp_server* server, int port);
-void test_tcp_server_poll(test_tcp_server* server, int seconds);
+void test_tcp_server_poll(test_tcp_server* server, int milliseconds);
 void test_tcp_server_destroy(test_tcp_server* server);
 
 #endif /* GRPC_TEST_CORE_UTIL_TEST_TCP_SERVER_H */
diff --git a/test/cpp/client/client_channel_stress_test.cc b/test/cpp/client/client_channel_stress_test.cc
index 91419cb..7b4d472 100644
--- a/test/cpp/client/client_channel_stress_test.cc
+++ b/test/cpp/client/client_channel_stress_test.cc
@@ -31,6 +31,7 @@
 #include <grpcpp/channel.h>
 #include <grpcpp/client_context.h>
 #include <grpcpp/create_channel.h>
+#include <grpcpp/impl/codegen/sync.h>
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
 
@@ -168,24 +169,24 @@
     explicit ServerThread(const grpc::string& type,
                           const grpc::string& server_host, T* service)
         : type_(type), service_(service) {
-      std::mutex mu;
+      grpc::internal::Mutex mu;
       // We need to acquire the lock here in order to prevent the notify_one
       // by ServerThread::Start from firing before the wait below is hit.
-      std::unique_lock<std::mutex> lock(mu);
+      grpc::internal::MutexLock lock(&mu);
       port_ = grpc_pick_unused_port_or_die();
       gpr_log(GPR_INFO, "starting %s server on port %d", type_.c_str(), port_);
-      std::condition_variable cond;
+      grpc::internal::CondVar cond;
       thread_.reset(new std::thread(
           std::bind(&ServerThread::Start, this, server_host, &mu, &cond)));
-      cond.wait(lock);
+      cond.Wait(&mu);
       gpr_log(GPR_INFO, "%s server startup complete", type_.c_str());
     }
 
-    void Start(const grpc::string& server_host, std::mutex* mu,
-               std::condition_variable* cond) {
+    void Start(const grpc::string& server_host, grpc::internal::Mutex* mu,
+               grpc::internal::CondVar* cond) {
       // We need to acquire the lock here in order to prevent the notify_one
       // below from firing before its corresponding wait is executed.
-      std::lock_guard<std::mutex> lock(*mu);
+      grpc::internal::MutexLock lock(mu);
       std::ostringstream server_address;
       server_address << server_host << ":" << port_;
       ServerBuilder builder;
@@ -193,7 +194,7 @@
                                InsecureServerCredentials());
       builder.RegisterService(service_);
       server_ = builder.BuildAndStart();
-      cond->notify_one();
+      cond->Signal();
     }
 
     void Shutdown() {
@@ -267,8 +268,8 @@
                     response_generator_.get());
     std::ostringstream uri;
     uri << "fake:///servername_not_used";
-    channel_ =
-        CreateCustomChannel(uri.str(), InsecureChannelCredentials(), args);
+    channel_ = ::grpc::CreateCustomChannel(uri.str(),
+                                           InsecureChannelCredentials(), args);
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
diff --git a/test/cpp/codegen/BUILD b/test/cpp/codegen/BUILD
index 558e5e7..90af67b 100644
--- a/test/cpp/codegen/BUILD
+++ b/test/cpp/codegen/BUILD
@@ -66,6 +66,7 @@
     deps = [
         "//:grpc++",
         "//test/core/util:grpc_test_util",
+        "//test/cpp/util:test_config",
     ],
 )
 
diff --git a/test/cpp/codegen/compiler_test_golden b/test/cpp/codegen/compiler_test_golden
index 7f9fd29..e571a95 100644
--- a/test/cpp/codegen/compiler_test_golden
+++ b/test/cpp/codegen/compiler_test_golden
@@ -40,10 +40,20 @@
 #include <grpcpp/impl/codegen/stub_options.h>
 #include <grpcpp/impl/codegen/sync_stream.h>
 
-namespace grpc {
-class CompletionQueue;
+namespace grpc_impl {
 class Channel;
+class CompletionQueue;
 class ServerCompletionQueue;
+}  // namespace grpc_impl
+
+namespace grpc {
+namespace experimental {
+template <typename RequestT, typename ResponseT>
+class MessageAllocator;
+}  // namespace experimental
+}  // namespace grpc_impl
+
+namespace grpc {
 class ServerContext;
 }  // namespace grpc
 
@@ -114,6 +124,8 @@
       // MethodA1 leading comment 1
       virtual void MethodA1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) = 0;
       virtual void MethodA1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) = 0;
+      virtual void MethodA1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) = 0;
+      virtual void MethodA1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) = 0;
       // MethodA1 trailing comment 1
       // MethodA2 detached leading comment 1
       //
@@ -184,6 +196,8 @@
      public:
       void MethodA1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) override;
       void MethodA1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) override;
+      void MethodA1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) override;
+      void MethodA1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) override;
       void MethodA2(::grpc::ClientContext* context, ::grpc::testing::Response* response, ::grpc::experimental::ClientWriteReactor< ::grpc::testing::Request>* reactor) override;
       void MethodA3(::grpc::ClientContext* context, ::grpc::testing::Request* request, ::grpc::experimental::ClientReadReactor< ::grpc::testing::Response>* reactor) override;
       void MethodA4(::grpc::ClientContext* context, ::grpc::experimental::ClientBidiReactor< ::grpc::testing::Request,::grpc::testing::Response>* reactor) override;
@@ -332,6 +346,12 @@
                    return this->MethodA1(context, request, response, controller);
                  }));
     }
+    void SetMessageAllocatorFor_MethodA1(
+        ::grpc::experimental::MessageAllocator< ::grpc::testing::Request, ::grpc::testing::Response>* allocator) {
+      static_cast<::grpc::internal::CallbackUnaryHandler< ::grpc::testing::Request, ::grpc::testing::Response>*>(
+          ::grpc::Service::experimental().GetHandler(0))
+              ->SetMessageAllocator(allocator);
+    }
     ~ExperimentalWithCallbackMethod_MethodA1() override {
       BaseClassMustBeDerivedFromService(this);
     }
@@ -717,6 +737,8 @@
       // MethodB1 leading comment 1
       virtual void MethodB1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) = 0;
       virtual void MethodB1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) = 0;
+      virtual void MethodB1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) = 0;
+      virtual void MethodB1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) = 0;
       // MethodB1 trailing comment 1
     };
     virtual class experimental_async_interface* experimental_async() { return nullptr; }
@@ -739,6 +761,8 @@
      public:
       void MethodB1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) override;
       void MethodB1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) override;
+      void MethodB1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) override;
+      void MethodB1(::grpc::ClientContext* context, const ::grpc::ByteBuffer* request, ::grpc::testing::Response* response, ::grpc::experimental::ClientUnaryReactor* reactor) override;
      private:
       friend class Stub;
       explicit experimental_async(Stub* stub): stub_(stub) { }
@@ -800,6 +824,12 @@
                    return this->MethodB1(context, request, response, controller);
                  }));
     }
+    void SetMessageAllocatorFor_MethodB1(
+        ::grpc::experimental::MessageAllocator< ::grpc::testing::Request, ::grpc::testing::Response>* allocator) {
+      static_cast<::grpc::internal::CallbackUnaryHandler< ::grpc::testing::Request, ::grpc::testing::Response>*>(
+          ::grpc::Service::experimental().GetHandler(0))
+              ->SetMessageAllocator(allocator);
+    }
     ~ExperimentalWithCallbackMethod_MethodB1() override {
       BaseClassMustBeDerivedFromService(this);
     }
diff --git a/test/cpp/codegen/golden_file_test.cc b/test/cpp/codegen/golden_file_test.cc
index bfd3649..8951060 100644
--- a/test/cpp/codegen/golden_file_test.cc
+++ b/test/cpp/codegen/golden_file_test.cc
@@ -22,6 +22,8 @@
 #include <gflags/gflags.h>
 #include <gtest/gtest.h>
 
+#include "test/cpp/util/test_config.h"
+
 // In some distros, gflags is in the namespace google, and in some others,
 // in gflags. This hack is enabling us to find both.
 namespace google {}
@@ -31,7 +33,7 @@
 
 DEFINE_string(
     generated_file_path, "",
-    "path to the directory containing generated files compiler_test.grpc.pb.h"
+    "path to the directory containing generated files compiler_test.grpc.pb.h "
     "and compiler_test_mock.grpc.pb.h");
 
 const char kGoldenFilePath[] = "test/cpp/codegen/compiler_test_golden";
@@ -67,7 +69,7 @@
 
 int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
-  ParseCommandLineFlags(&argc, &argv, true);
+  grpc::testing::InitTest(&argc, &argv, true);
   if (FLAGS_generated_file_path.empty()) {
     FLAGS_generated_file_path = "gens/src/proto/grpc/testing/";
   }
diff --git a/test/cpp/end2end/BUILD b/test/cpp/end2end/BUILD
index 707a628..519a33f 100644
--- a/test/cpp/end2end/BUILD
+++ b/test/cpp/end2end/BUILD
@@ -140,6 +140,7 @@
         "//src/proto/grpc/testing:echo_proto",
         "//src/proto/grpc/testing/duplicate:echo_duplicate_proto",
         "//test/core/util:grpc_test_util",
+        "//test/cpp/util:test_config",
         "//test/cpp/util:test_util",
     ],
 )
@@ -423,6 +424,26 @@
 )
 
 grpc_cc_test(
+    name = "service_config_end2end_test",
+    srcs = ["service_config_end2end_test.cc"],
+    external_deps = [
+        "gmock",
+        "gtest",
+    ],
+    deps = [
+        ":test_service_impl",
+        "//:gpr",
+        "//:grpc",
+        "//:grpc++",
+        "//src/proto/grpc/testing:echo_messages_proto",
+        "//src/proto/grpc/testing:echo_proto",
+        "//src/proto/grpc/testing/duplicate:echo_duplicate_proto",
+        "//test/core/util:grpc_test_util",
+        "//test/cpp/util:test_util",
+    ],
+)
+
+grpc_cc_test(
     name = "grpclb_end2end_test",
     srcs = ["grpclb_end2end_test.cc"],
     external_deps = [
@@ -544,6 +565,7 @@
         "//src/proto/grpc/testing:echo_proto",
         "//src/proto/grpc/testing/duplicate:echo_duplicate_proto",
         "//test/core/util:grpc_test_util",
+        "//test/cpp/util:test_config",
         "//test/cpp/util:test_util",
     ],
 )
@@ -675,3 +697,23 @@
         "//test/cpp/util:test_util",
     ],
 )
+
+grpc_cc_test(
+    name = "message_allocator_end2end_test",
+    srcs = ["message_allocator_end2end_test.cc"],
+    external_deps = [
+        "gtest",
+    ],
+    deps = [
+        ":test_service_impl",
+        "//:grpc",
+        "//:gpr",
+        "//:grpc++",
+        "//src/proto/grpc/testing:echo_messages_proto",
+        "//src/proto/grpc/testing:echo_proto",
+        "//src/proto/grpc/testing:simple_messages_proto",
+        "//test/core/util:grpc_test_util",
+        "//test/cpp/util:test_util",
+    ],
+)
+
diff --git a/test/cpp/end2end/async_end2end_test.cc b/test/cpp/end2end/async_end2end_test.cc
index e09f54d..6ca0edf 100644
--- a/test/cpp/end2end/async_end2end_test.cc
+++ b/test/cpp/end2end/async_end2end_test.cc
@@ -32,7 +32,7 @@
 #include <grpcpp/server_builder.h>
 #include <grpcpp/server_context.h>
 
-#include "src/core/lib/gpr/env.h"
+#include "src/core/ext/filters/client_channel/backup_poller.h"
 #include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/iomgr/port.h"
 #include "src/proto/grpc/health/v1/health.grpc.pb.h"
@@ -43,6 +43,10 @@
 #include "test/cpp/util/string_ref_helper.h"
 #include "test/cpp/util/test_credentials_provider.h"
 
+#ifdef GRPC_POSIX_SOCKET
+#include "src/core/lib/iomgr/ev_posix.h"
+#endif  // GRPC_POSIX_SOCKET
+
 #include <gtest/gtest.h>
 
 using grpc::testing::EchoRequest;
@@ -294,9 +298,9 @@
     auto channel_creds = GetCredentialsProvider()->GetChannelCredentials(
         GetParam().credentials_type, &args);
     std::shared_ptr<Channel> channel =
-        !(GetParam().inproc)
-            ? CreateCustomChannel(server_address_.str(), channel_creds, args)
-            : server_->InProcessChannel(args);
+        !(GetParam().inproc) ? ::grpc::CreateCustomChannel(
+                                   server_address_.str(), channel_creds, args)
+                             : server_->InProcessChannel(args);
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
 
@@ -358,13 +362,14 @@
     return;
   }
   int poller_slowdown_factor = 1;
+#ifdef GRPC_POSIX_SOCKET
   // It needs 2 pollset_works to reconnect the channel with polling engine
   // "poll"
-  char* s = gpr_getenv("GRPC_POLL_STRATEGY");
-  if (s != nullptr && 0 == strcmp(s, "poll")) {
+  grpc_core::UniquePtr<char> poller = GPR_GLOBAL_CONFIG_GET(grpc_poll_strategy);
+  if (0 == strcmp(poller.get(), "poll")) {
     poller_slowdown_factor = 2;
   }
-  gpr_free(s);
+#endif  // GRPC_POSIX_SOCKET
   ResetStub();
   SendRpc(1);
   server_->Shutdown();
@@ -1255,9 +1260,9 @@
   const auto& channel_creds = GetCredentialsProvider()->GetChannelCredentials(
       GetParam().credentials_type, &args);
   std::shared_ptr<Channel> channel =
-      !(GetParam().inproc)
-          ? CreateCustomChannel(server_address_.str(), channel_creds, args)
-          : server_->InProcessChannel(args);
+      !(GetParam().inproc) ? ::grpc::CreateCustomChannel(server_address_.str(),
+                                                         channel_creds, args)
+                           : server_->InProcessChannel(args);
   std::unique_ptr<grpc::testing::UnimplementedEchoService::Stub> stub;
   stub = grpc::testing::UnimplementedEchoService::NewStub(channel);
   EchoRequest send_request;
@@ -1883,7 +1888,7 @@
 int main(int argc, char** argv) {
   // Change the backup poll interval from 5s to 100ms to speed up the
   // ReconnectChannel test
-  gpr_setenv("GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS", "100");
+  GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 100);
   grpc::testing::TestEnvironment env(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   int ret = RUN_ALL_TESTS();
diff --git a/test/cpp/end2end/cfstream_test.cc b/test/cpp/end2end/cfstream_test.cc
index a6ed7c6..59cf98f 100644
--- a/test/cpp/end2end/cfstream_test.cc
+++ b/test/cpp/end2end/cfstream_test.cc
@@ -45,6 +45,7 @@
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 #include "test/cpp/end2end/test_service_impl.h"
+#include "test/cpp/util/test_credentials_provider.h"
 
 #ifdef GRPC_CFSTREAM
 using grpc::ClientAsyncResponseReader;
@@ -57,13 +58,19 @@
 namespace testing {
 namespace {
 
-class CFStreamTest : public ::testing::Test {
+struct TestScenario {
+  TestScenario(const grpc::string& creds_type, const grpc::string& content)
+      : credentials_type(creds_type), message_content(content) {}
+  const grpc::string credentials_type;
+  const grpc::string message_content;
+};
+
+class CFStreamTest : public ::testing::TestWithParam<TestScenario> {
  protected:
   CFStreamTest()
       : server_host_("grpctest"),
         interface_("lo0"),
-        ipv4_address_("127.0.0.2"),
-        kRequestMessage_("🖖") {}
+        ipv4_address_("10.0.0.1") {}
 
   void DNSUp() {
     std::ostringstream cmd;
@@ -118,7 +125,7 @@
 
   void StartServer() {
     port_ = grpc_pick_unused_port_or_die();
-    server_.reset(new ServerData(port_));
+    server_.reset(new ServerData(port_, GetParam().credentials_type));
     server_->Start(server_host_);
   }
   void StopServer() { server_->Shutdown(); }
@@ -131,8 +138,10 @@
   std::shared_ptr<Channel> BuildChannel() {
     std::ostringstream server_address;
     server_address << server_host_ << ":" << port_;
-    return CreateCustomChannel(
-        server_address.str(), InsecureChannelCredentials(), ChannelArguments());
+    ChannelArguments args;
+    auto channel_creds = GetCredentialsProvider()->GetChannelCredentials(
+        GetParam().credentials_type, &args);
+    return CreateCustomChannel(server_address.str(), channel_creds, args);
   }
 
   void SendRpc(
@@ -140,11 +149,12 @@
       bool expect_success = false) {
     auto response = std::unique_ptr<EchoResponse>(new EchoResponse());
     EchoRequest request;
-    request.set_message(kRequestMessage_);
+    auto& msg = GetParam().message_content;
+    request.set_message(msg);
     ClientContext context;
     Status status = stub->Echo(&context, request, response.get());
     if (status.ok()) {
-      gpr_log(GPR_DEBUG, "RPC returned %s\n", response->message().c_str());
+      EXPECT_EQ(msg, response->message());
     } else {
       gpr_log(GPR_DEBUG, "RPC failed: %s", status.error_message().c_str());
     }
@@ -156,9 +166,7 @@
       const std::unique_ptr<grpc::testing::EchoTestService::Stub>& stub,
       RequestParams param = RequestParams()) {
     EchoRequest request;
-    auto msg = std::to_string(ctr.load());
-    request.set_message(msg);
-    ctr++;
+    request.set_message(GetParam().message_content);
     *request.mutable_param() = std::move(param);
     AsyncClientCall* call = new AsyncClientCall;
 
@@ -166,7 +174,6 @@
         stub->PrepareAsyncEcho(&call->context, request, &cq_);
 
     call->response_reader->StartCall();
-    gpr_log(GPR_DEBUG, "Sending request: %s", msg.c_str());
     call->response_reader->Finish(&call->reply, &call->status, (void*)call);
   }
 
@@ -206,12 +213,14 @@
  private:
   struct ServerData {
     int port_;
+    const grpc::string creds_;
     std::unique_ptr<Server> server_;
     TestServiceImpl service_;
     std::unique_ptr<std::thread> thread_;
     bool server_ready_ = false;
 
-    explicit ServerData(int port) { port_ = port; }
+    ServerData(int port, const grpc::string& creds)
+        : port_(port), creds_(creds) {}
 
     void Start(const grpc::string& server_host) {
       gpr_log(GPR_INFO, "starting server on port %d", port_);
@@ -230,8 +239,9 @@
       std::ostringstream server_address;
       server_address << server_host << ":" << port_;
       ServerBuilder builder;
-      builder.AddListeningPort(server_address.str(),
-                               InsecureServerCredentials());
+      auto server_creds =
+          GetCredentialsProvider()->GetServerCredentials(creds_);
+      builder.AddListeningPort(server_address.str(), server_creds);
       builder.RegisterService(&service_);
       server_ = builder.BuildAndStart();
       std::lock_guard<std::mutex> lock(*mu);
@@ -251,13 +261,44 @@
   const grpc::string ipv4_address_;
   std::unique_ptr<ServerData> server_;
   int port_;
-  const grpc::string kRequestMessage_;
-  std::atomic_int ctr{0};
 };
 
+std::vector<TestScenario> CreateTestScenarios() {
+  std::vector<TestScenario> scenarios;
+  std::vector<grpc::string> credentials_types;
+  std::vector<grpc::string> messages;
+
+  credentials_types.push_back(kInsecureCredentialsType);
+  auto sec_list = GetCredentialsProvider()->GetSecureCredentialsTypeList();
+  for (auto sec = sec_list.begin(); sec != sec_list.end(); sec++) {
+    credentials_types.push_back(*sec);
+  }
+
+  messages.push_back("🖖");
+  for (size_t k = 1; k < GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH / 1024; k *= 32) {
+    grpc::string big_msg;
+    for (size_t i = 0; i < k * 1024; ++i) {
+      char c = 'a' + (i % 26);
+      big_msg += c;
+    }
+    messages.push_back(big_msg);
+  }
+  for (auto cred = credentials_types.begin(); cred != credentials_types.end();
+       ++cred) {
+    for (auto msg = messages.begin(); msg != messages.end(); msg++) {
+      scenarios.emplace_back(*cred, *msg);
+    }
+  }
+
+  return scenarios;
+}
+
+INSTANTIATE_TEST_CASE_P(CFStreamTest, CFStreamTest,
+                        ::testing::ValuesIn(CreateTestScenarios()));
+
 // gRPC should automatically detech network flaps (without enabling keepalives)
 //  when CFStream is enabled
-TEST_F(CFStreamTest, NetworkTransition) {
+TEST_P(CFStreamTest, NetworkTransition) {
   auto channel = BuildChannel();
   auto stub = BuildStub(channel);
   // Channel should be in READY state after we send an RPC
@@ -293,7 +334,7 @@
 }
 
 // Network flaps while RPCs are in flight
-TEST_F(CFStreamTest, NetworkFlapRpcsInFlight) {
+TEST_P(CFStreamTest, NetworkFlapRpcsInFlight) {
   auto channel = BuildChannel();
   auto stub = BuildStub(channel);
   std::atomic_int rpcs_sent{0};
@@ -318,9 +359,7 @@
       ++total_completions;
       GPR_ASSERT(ok);
       AsyncClientCall* call = static_cast<AsyncClientCall*>(got_tag);
-      if (call->status.ok()) {
-        gpr_log(GPR_DEBUG, "RPC response: %s", call->reply.message().c_str());
-      } else {
+      if (!call->status.ok()) {
         gpr_log(GPR_DEBUG, "RPC failed with error: %s",
                 call->status.error_message().c_str());
         // Bring network up when RPCs start failing
@@ -347,7 +386,7 @@
 
 // Send a bunch of RPCs, some of which are expected to fail.
 // We should get back a response for all RPCs
-TEST_F(CFStreamTest, ConcurrentRpc) {
+TEST_P(CFStreamTest, ConcurrentRpc) {
   auto channel = BuildChannel();
   auto stub = BuildStub(channel);
   std::atomic_int rpcs_sent{0};
@@ -361,9 +400,7 @@
       ++total_completions;
       GPR_ASSERT(ok);
       AsyncClientCall* call = static_cast<AsyncClientCall*>(got_tag);
-      if (call->status.ok()) {
-        gpr_log(GPR_DEBUG, "RPC response: %s", call->reply.message().c_str());
-      } else {
+      if (!call->status.ok()) {
         gpr_log(GPR_DEBUG, "RPC failed: %s",
                 call->status.error_message().c_str());
         // Bring network up when RPCs start failing
diff --git a/test/cpp/end2end/channelz_service_test.cc b/test/cpp/end2end/channelz_service_test.cc
index fe52a64..26ef59f 100644
--- a/test/cpp/end2end/channelz_service_test.cc
+++ b/test/cpp/end2end/channelz_service_test.cc
@@ -145,7 +145,7 @@
       ChannelArguments args;
       args.SetInt(GRPC_ARG_ENABLE_CHANNELZ, 1);
       args.SetInt(GRPC_ARG_MAX_CHANNEL_TRACE_EVENT_MEMORY_PER_NODE, 1024);
-      std::shared_ptr<Channel> channel_to_backend = CreateCustomChannel(
+      std::shared_ptr<Channel> channel_to_backend = ::grpc::CreateCustomChannel(
           backend_server_address, InsecureChannelCredentials(), args);
       proxy_service_.AddChannelToBackend(channel_to_backend);
     }
@@ -157,7 +157,7 @@
     // disable channelz. We only want to focus on proxy to backend outbound.
     args.SetInt(GRPC_ARG_ENABLE_CHANNELZ, 0);
     std::shared_ptr<Channel> channel =
-        CreateCustomChannel(target, InsecureChannelCredentials(), args);
+        ::grpc::CreateCustomChannel(target, InsecureChannelCredentials(), args);
     channelz_stub_ = grpc::channelz::v1::Channelz::NewStub(channel);
     echo_stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
@@ -171,7 +171,7 @@
     // This ensures that gRPC will not do connection sharing.
     args.SetInt("salt", salt++);
     std::shared_ptr<Channel> channel =
-        CreateCustomChannel(target, InsecureChannelCredentials(), args);
+        ::grpc::CreateCustomChannel(target, InsecureChannelCredentials(), args);
     return grpc::testing::EchoTestService::NewStub(channel);
   }
 
diff --git a/test/cpp/end2end/client_callback_end2end_test.cc b/test/cpp/end2end/client_callback_end2end_test.cc
index 821fcc2..ede24f3 100644
--- a/test/cpp/end2end/client_callback_end2end_test.cc
+++ b/test/cpp/end2end/client_callback_end2end_test.cc
@@ -142,8 +142,8 @@
     switch (GetParam().protocol) {
       case Protocol::TCP:
         if (!GetParam().use_interceptors) {
-          channel_ =
-              CreateCustomChannel(server_address_.str(), channel_creds, args);
+          channel_ = ::grpc::CreateCustomChannel(server_address_.str(),
+                                                 channel_creds, args);
         } else {
           channel_ = CreateCustomChannelWithInterceptors(
               server_address_.str(), channel_creds, args,
@@ -536,9 +536,9 @@
 struct ClientCancelInfo {
   bool cancel{false};
   int ops_before_cancel;
+
   ClientCancelInfo() : cancel{false} {}
-  // Allow the single-op version to be non-explicit for ease of use
-  ClientCancelInfo(int ops) : cancel{true}, ops_before_cancel{ops} {}
+  explicit ClientCancelInfo(int ops) : cancel{true}, ops_before_cancel{ops} {}
 };
 
 class WriteClient : public grpc::experimental::ClientWriteReactor<EchoRequest> {
@@ -651,7 +651,7 @@
 TEST_P(ClientCallbackEnd2endTest, ClientCancelsRequestStream) {
   MAYBE_SKIP_TEST;
   ResetStub();
-  WriteClient test{stub_.get(), DO_NOT_CANCEL, 3, {2}};
+  WriteClient test{stub_.get(), DO_NOT_CANCEL, 3, ClientCancelInfo{2}};
   test.Await();
   // Make sure that the server interceptors got the cancel
   if (GetParam().use_interceptors) {
@@ -696,6 +696,65 @@
   }
 }
 
+TEST_P(ClientCallbackEnd2endTest, UnaryReactor) {
+  MAYBE_SKIP_TEST;
+  ResetStub();
+  class UnaryClient : public grpc::experimental::ClientUnaryReactor {
+   public:
+    UnaryClient(grpc::testing::EchoTestService::Stub* stub) {
+      cli_ctx_.AddMetadata("key1", "val1");
+      cli_ctx_.AddMetadata("key2", "val2");
+      request_.mutable_param()->set_echo_metadata_initially(true);
+      request_.set_message("Hello metadata");
+      stub->experimental_async()->Echo(&cli_ctx_, &request_, &response_, this);
+      StartCall();
+    }
+    void OnReadInitialMetadataDone(bool ok) override {
+      EXPECT_TRUE(ok);
+      EXPECT_EQ(1u, cli_ctx_.GetServerInitialMetadata().count("key1"));
+      EXPECT_EQ(
+          "val1",
+          ToString(cli_ctx_.GetServerInitialMetadata().find("key1")->second));
+      EXPECT_EQ(1u, cli_ctx_.GetServerInitialMetadata().count("key2"));
+      EXPECT_EQ(
+          "val2",
+          ToString(cli_ctx_.GetServerInitialMetadata().find("key2")->second));
+      initial_metadata_done_ = true;
+    }
+    void OnDone(const Status& s) override {
+      EXPECT_TRUE(initial_metadata_done_);
+      EXPECT_EQ(0u, cli_ctx_.GetServerTrailingMetadata().size());
+      EXPECT_TRUE(s.ok());
+      EXPECT_EQ(request_.message(), response_.message());
+      std::unique_lock<std::mutex> l(mu_);
+      done_ = true;
+      cv_.notify_one();
+    }
+    void Await() {
+      std::unique_lock<std::mutex> l(mu_);
+      while (!done_) {
+        cv_.wait(l);
+      }
+    }
+
+   private:
+    EchoRequest request_;
+    EchoResponse response_;
+    ClientContext cli_ctx_;
+    std::mutex mu_;
+    std::condition_variable cv_;
+    bool done_{false};
+    bool initial_metadata_done_{false};
+  };
+
+  UnaryClient test{stub_.get()};
+  test.Await();
+  // Make sure that the server interceptors were not notified of a cancel
+  if (GetParam().use_interceptors) {
+    EXPECT_EQ(0, DummyInterceptor::GetNumTimesCancel());
+  }
+}
+
 class ReadClient : public grpc::experimental::ClientReadReactor<EchoResponse> {
  public:
   ReadClient(grpc::testing::EchoTestService::Stub* stub,
@@ -810,7 +869,7 @@
 TEST_P(ClientCallbackEnd2endTest, ClientCancelsResponseStream) {
   MAYBE_SKIP_TEST;
   ResetStub();
-  ReadClient test{stub_.get(), DO_NOT_CANCEL, 2};
+  ReadClient test{stub_.get(), DO_NOT_CANCEL, ClientCancelInfo{2}};
   test.Await();
   // Because cancel in this case races with server finish, we can't be sure that
   // server interceptors even see cancellation
@@ -993,7 +1052,7 @@
   MAYBE_SKIP_TEST;
   ResetStub();
   BidiClient test{stub_.get(), DO_NOT_CANCEL,
-                  kServerDefaultResponseStreamsToSend, 2};
+                  kServerDefaultResponseStreamsToSend, ClientCancelInfo{2}};
   test.Await();
   // Make sure that the server interceptors were notified of a cancel
   if (GetParam().use_interceptors) {
@@ -1094,7 +1153,8 @@
       GetParam().credentials_type, &args);
   std::shared_ptr<Channel> channel =
       (GetParam().protocol == Protocol::TCP)
-          ? CreateCustomChannel(server_address_.str(), channel_creds, args)
+          ? ::grpc::CreateCustomChannel(server_address_.str(), channel_creds,
+                                        args)
           : server_->InProcessChannel(args);
   std::unique_ptr<grpc::testing::UnimplementedEchoService::Stub> stub;
   stub = grpc::testing::UnimplementedEchoService::NewStub(channel);
diff --git a/test/cpp/end2end/client_crash_test.cc b/test/cpp/end2end/client_crash_test.cc
index 992f3c4..2d5c1b4 100644
--- a/test/cpp/end2end/client_crash_test.cc
+++ b/test/cpp/end2end/client_crash_test.cc
@@ -60,7 +60,7 @@
     }));
     GPR_ASSERT(server_);
     return grpc::testing::EchoTestService::NewStub(
-        CreateChannel(addr, InsecureChannelCredentials()));
+        grpc::CreateChannel(addr, InsecureChannelCredentials()));
   }
 
   void KillServer() { server_.reset(); }
diff --git a/test/cpp/end2end/client_crash_test_server.cc b/test/cpp/end2end/client_crash_test_server.cc
index cb4afd7..d92f9c5 100644
--- a/test/cpp/end2end/client_crash_test_server.cc
+++ b/test/cpp/end2end/client_crash_test_server.cc
@@ -27,6 +27,7 @@
 #include <grpcpp/server_context.h>
 
 #include "src/proto/grpc/testing/echo.grpc.pb.h"
+#include "test/cpp/util/test_config.h"
 
 DEFINE_string(address, "", "Address to bind to");
 
@@ -72,7 +73,7 @@
 }  // namespace grpc
 
 int main(int argc, char** argv) {
-  ParseCommandLineFlags(&argc, &argv, true);
+  grpc::testing::InitTest(&argc, &argv, true);
   grpc::testing::RunServer();
 
   return 0;
diff --git a/test/cpp/end2end/client_interceptors_end2end_test.cc b/test/cpp/end2end/client_interceptors_end2end_test.cc
index 421b31a..f1aed09 100644
--- a/test/cpp/end2end/client_interceptors_end2end_test.cc
+++ b/test/cpp/end2end/client_interceptors_end2end_test.cc
@@ -611,6 +611,18 @@
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
 }
 
+TEST_F(ClientInterceptorsEnd2endTest,
+       LameChannelClientInterceptorHijackingTest) {
+  ChannelArguments args;
+  std::vector<std::unique_ptr<experimental::ClientInterceptorFactoryInterface>>
+      creators;
+  creators.push_back(std::unique_ptr<HijackingInterceptorFactory>(
+      new HijackingInterceptorFactory()));
+  auto channel = experimental::CreateCustomChannelWithInterceptors(
+      server_address_, nullptr, args, std::move(creators));
+  MakeCall(channel);
+}
+
 TEST_F(ClientInterceptorsEnd2endTest, ClientInterceptorHijackingTest) {
   ChannelArguments args;
   DummyInterceptor::Reset();
diff --git a/test/cpp/end2end/client_lb_end2end_test.cc b/test/cpp/end2end/client_lb_end2end_test.cc
index 77f9db9..766c38a 100644
--- a/test/cpp/end2end/client_lb_end2end_test.cc
+++ b/test/cpp/end2end/client_lb_end2end_test.cc
@@ -33,16 +33,17 @@
 #include <grpcpp/client_context.h>
 #include <grpcpp/create_channel.h>
 #include <grpcpp/health_check_service_interface.h>
+#include <grpcpp/impl/codegen/sync.h>
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
 
+#include "src/core/ext/filters/client_channel/backup_poller.h"
 #include "src/core/ext/filters/client_channel/global_subchannel_pool.h"
 #include "src/core/ext/filters/client_channel/parse_address.h"
 #include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/lib/backoff/backoff.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gprpp/debug_location.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/tcp_client.h"
@@ -98,7 +99,7 @@
   Status Echo(ServerContext* context, const EchoRequest* request,
               EchoResponse* response) override {
     {
-      std::unique_lock<std::mutex> lock(mu_);
+      grpc::internal::MutexLock lock(&mu_);
       ++request_count_;
     }
     AddClient(context->peer());
@@ -106,29 +107,29 @@
   }
 
   int request_count() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     return request_count_;
   }
 
   void ResetCounters() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     request_count_ = 0;
   }
 
   std::set<grpc::string> clients() {
-    std::unique_lock<std::mutex> lock(clients_mu_);
+    grpc::internal::MutexLock lock(&clients_mu_);
     return clients_;
   }
 
  private:
   void AddClient(const grpc::string& client) {
-    std::unique_lock<std::mutex> lock(clients_mu_);
+    grpc::internal::MutexLock lock(&clients_mu_);
     clients_.insert(client);
   }
 
-  std::mutex mu_;
+  grpc::internal::Mutex mu_;
   int request_count_;
-  std::mutex clients_mu_;
+  grpc::internal::Mutex clients_mu_;
   std::set<grpc::string> clients_;
 };
 
@@ -141,7 +142,7 @@
             grpc_fake_transport_security_credentials_create())) {
     // Make the backup poller poll very frequently in order to pick up
     // updates from all the subchannels's FDs.
-    gpr_setenv("GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS", "1");
+    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
   }
 
   void SetUp() override {
@@ -236,7 +237,7 @@
     }  // else, default to pick first
     args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
                     response_generator_.get());
-    return CreateCustomChannel("fake:///", creds_, args);
+    return ::grpc::CreateCustomChannel("fake:///", creds_, args);
   }
 
   bool SendRpc(
@@ -293,18 +294,18 @@
     void Start(const grpc::string& server_host) {
       gpr_log(GPR_INFO, "starting server on port %d", port_);
       started_ = true;
-      std::mutex mu;
-      std::unique_lock<std::mutex> lock(mu);
-      std::condition_variable cond;
+      grpc::internal::Mutex mu;
+      grpc::internal::MutexLock lock(&mu);
+      grpc::internal::CondVar cond;
       thread_.reset(new std::thread(
           std::bind(&ServerData::Serve, this, server_host, &mu, &cond)));
-      cond.wait(lock, [this] { return server_ready_; });
+      cond.WaitUntil(&mu, [this] { return server_ready_; });
       server_ready_ = false;
       gpr_log(GPR_INFO, "server startup complete");
     }
 
-    void Serve(const grpc::string& server_host, std::mutex* mu,
-               std::condition_variable* cond) {
+    void Serve(const grpc::string& server_host, grpc::internal::Mutex* mu,
+               grpc::internal::CondVar* cond) {
       std::ostringstream server_address;
       server_address << server_host << ":" << port_;
       ServerBuilder builder;
@@ -313,9 +314,9 @@
       builder.AddListeningPort(server_address.str(), std::move(creds));
       builder.RegisterService(&service_);
       server_ = builder.BuildAndStart();
-      std::lock_guard<std::mutex> lock(*mu);
+      grpc::internal::MutexLock lock(mu);
       server_ready_ = true;
-      cond->notify_one();
+      cond->Signal();
     }
 
     void Shutdown() {
@@ -1374,7 +1375,7 @@
   void TearDown() override { ClientLbEnd2endTest::TearDown(); }
 
   int trailers_intercepted() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     return trailers_intercepted_;
   }
 
@@ -1382,11 +1383,11 @@
   static void ReportTrailerIntercepted(void* arg) {
     ClientLbInterceptTrailingMetadataTest* self =
         static_cast<ClientLbInterceptTrailingMetadataTest*>(arg);
-    std::unique_lock<std::mutex> lock(self->mu_);
+    grpc::internal::MutexLock lock(&self->mu_);
     self->trailers_intercepted_++;
   }
 
-  std::mutex mu_;
+  grpc::internal::Mutex mu_;
   int trailers_intercepted_ = 0;
 };
 
diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc
index 40023c7..8fae2da 100644
--- a/test/cpp/end2end/end2end_test.cc
+++ b/test/cpp/end2end/end2end_test.cc
@@ -34,7 +34,7 @@
 #include <grpcpp/server_builder.h>
 #include <grpcpp/server_context.h>
 
-#include "src/core/lib/gpr/env.h"
+#include "src/core/ext/filters/client_channel/backup_poller.h"
 #include "src/core/lib/iomgr/iomgr.h"
 #include "src/core/lib/security/credentials/credentials.h"
 #include "src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.h"
@@ -46,6 +46,10 @@
 #include "test/cpp/util/string_ref_helper.h"
 #include "test/cpp/util/test_credentials_provider.h"
 
+#ifdef GRPC_POSIX_SOCKET
+#include "src/core/lib/iomgr/ev_posix.h"
+#endif  // GRPC_POSIX_SOCKET
+
 #include <gtest/gtest.h>
 
 using grpc::testing::EchoRequest;
@@ -129,7 +133,7 @@
   TestAuthMetadataProcessor(bool is_blocking) : is_blocking_(is_blocking) {}
 
   std::shared_ptr<CallCredentials> GetCompatibleClientCreds() {
-    return MetadataCredentialsFromPlugin(
+    return grpc::MetadataCredentialsFromPlugin(
         std::unique_ptr<MetadataCredentialsPlugin>(
             new TestMetadataCredentialsPlugin(
                 TestMetadataCredentialsPlugin::kGoodMetadataKey, kGoodGuy,
@@ -137,7 +141,7 @@
   }
 
   std::shared_ptr<CallCredentials> GetIncompatibleClientCreds() {
-    return MetadataCredentialsFromPlugin(
+    return grpc::MetadataCredentialsFromPlugin(
         std::unique_ptr<MetadataCredentialsPlugin>(
             new TestMetadataCredentialsPlugin(
                 TestMetadataCredentialsPlugin::kGoodMetadataKey, "Mr Hyde",
@@ -340,8 +344,8 @@
 
     if (!GetParam().inproc) {
       if (!GetParam().use_interceptors) {
-        channel_ =
-            CreateCustomChannel(server_address_.str(), channel_creds, args);
+        channel_ = ::grpc::CreateCustomChannel(server_address_.str(),
+                                               channel_creds, args);
       } else {
         channel_ = CreateCustomChannelWithInterceptors(
             server_address_.str(), channel_creds, args,
@@ -374,7 +378,8 @@
 
       proxy_server_ = builder.BuildAndStart();
 
-      channel_ = CreateChannel(proxyaddr.str(), InsecureChannelCredentials());
+      channel_ =
+          grpc::CreateChannel(proxyaddr.str(), InsecureChannelCredentials());
     }
 
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
@@ -807,11 +812,12 @@
   int poller_slowdown_factor = 1;
   // It needs 2 pollset_works to reconnect the channel with polling engine
   // "poll"
-  char* s = gpr_getenv("GRPC_POLL_STRATEGY");
-  if (s != nullptr && 0 == strcmp(s, "poll")) {
+#ifdef GRPC_POSIX_SOCKET
+  grpc_core::UniquePtr<char> poller = GPR_GLOBAL_CONFIG_GET(grpc_poll_strategy);
+  if (0 == strcmp(poller.get(), "poll")) {
     poller_slowdown_factor = 2;
   }
-  gpr_free(s);
+#endif  // GRPC_POSIX_SOCKET
   ResetStub();
   SendRpc(stub_.get(), 1, false);
   RestartServer(std::shared_ptr<AuthMetadataProcessor>());
@@ -1277,7 +1283,7 @@
   server_address << "127.0.0.1:" << port;
   // Channel to non-existing server
   auto channel =
-      CreateChannel(server_address.str(), InsecureChannelCredentials());
+      grpc::CreateChannel(server_address.str(), InsecureChannelCredentials());
   // Start IDLE
   EXPECT_EQ(GRPC_CHANNEL_IDLE, channel->GetState(true));
 
@@ -1420,18 +1426,18 @@
   EchoResponse response;
   request.set_message("Hello");
   request.mutable_param()->set_skip_cancelled_check(true);
-  // Let server sleep for 80 ms first to give the cancellation a chance.
-  // This is split into 40 ms to start the cancel and 40 ms extra time for
+  // Let server sleep for 200 ms first to give the cancellation a chance.
+  // This is split into 100 ms to start the cancel and 100 ms extra time for
   // it to make it to the server, to make it highly probable that the server
   // RPC would have already started by the time the cancellation is sent
   // and the server-side gets enough time to react to it.
-  request.mutable_param()->set_server_sleep_us(80 * 1000);
+  request.mutable_param()->set_server_sleep_us(200000);
 
   std::thread echo_thread{[this, &context, &request, &response] {
     Status s = stub_->Echo(&context, request, &response);
     EXPECT_EQ(StatusCode::CANCELLED, s.error_code());
   }};
-  std::this_thread::sleep_for(std::chrono::microseconds(40000));
+  std::this_thread::sleep_for(std::chrono::microseconds(100000));
   context.TryCancel();
   echo_thread.join();
 }
@@ -1825,8 +1831,8 @@
   EchoRequest request;
   EchoResponse response;
   ClientContext context;
-  context.set_credentials(
-      MetadataCredentialsFromPlugin(std::unique_ptr<MetadataCredentialsPlugin>(
+  context.set_credentials(grpc::MetadataCredentialsFromPlugin(
+      std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kBadMetadataKey,
               "Does not matter, will fail the key is invalid.", false, true))));
@@ -1843,8 +1849,8 @@
   EchoRequest request;
   EchoResponse response;
   ClientContext context;
-  context.set_credentials(
-      MetadataCredentialsFromPlugin(std::unique_ptr<MetadataCredentialsPlugin>(
+  context.set_credentials(grpc::MetadataCredentialsFromPlugin(
+      std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
               "With illegal \n value.", false, true))));
@@ -1861,8 +1867,8 @@
   EchoRequest request;
   EchoResponse response;
   ClientContext context;
-  context.set_credentials(
-      MetadataCredentialsFromPlugin(std::unique_ptr<MetadataCredentialsPlugin>(
+  context.set_credentials(grpc::MetadataCredentialsFromPlugin(
+      std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
               "Does not matter, will fail anyway (see 3rd param)", false,
@@ -1925,8 +1931,8 @@
   EchoRequest request;
   EchoResponse response;
   ClientContext context;
-  context.set_credentials(
-      MetadataCredentialsFromPlugin(std::unique_ptr<MetadataCredentialsPlugin>(
+  context.set_credentials(grpc::MetadataCredentialsFromPlugin(
+      std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
               "Does not matter, will fail anyway (see 3rd param)", true,
@@ -1952,13 +1958,15 @@
   const char kMetadataVal1[] = "call-creds-val1";
   const char kMetadataVal2[] = "call-creds-val2";
 
-  context.set_credentials(CompositeCallCredentials(
-      MetadataCredentialsFromPlugin(std::unique_ptr<MetadataCredentialsPlugin>(
-          new TestMetadataCredentialsPlugin(kMetadataKey1, kMetadataVal1, true,
-                                            true))),
-      MetadataCredentialsFromPlugin(std::unique_ptr<MetadataCredentialsPlugin>(
-          new TestMetadataCredentialsPlugin(kMetadataKey2, kMetadataVal2, true,
-                                            true)))));
+  context.set_credentials(grpc::CompositeCallCredentials(
+      grpc::MetadataCredentialsFromPlugin(
+          std::unique_ptr<MetadataCredentialsPlugin>(
+              new TestMetadataCredentialsPlugin(kMetadataKey1, kMetadataVal1,
+                                                true, true))),
+      grpc::MetadataCredentialsFromPlugin(
+          std::unique_ptr<MetadataCredentialsPlugin>(
+              new TestMetadataCredentialsPlugin(kMetadataKey2, kMetadataVal2,
+                                                true, true)))));
   request.set_message("Hello");
   request.mutable_param()->set_echo_metadata(true);
 
@@ -2104,7 +2112,7 @@
 }  // namespace grpc
 
 int main(int argc, char** argv) {
-  gpr_setenv("GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS", "200");
+  GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 200);
   grpc::testing::TestEnvironment env(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
diff --git a/test/cpp/end2end/filter_end2end_test.cc b/test/cpp/end2end/filter_end2end_test.cc
index ad67402..a4c981a 100644
--- a/test/cpp/end2end/filter_end2end_test.cc
+++ b/test/cpp/end2end/filter_end2end_test.cc
@@ -146,8 +146,8 @@
   }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     generic_stub_.reset(new GenericStub(channel));
     ResetConnectionCounter();
     ResetCallCounter();
diff --git a/test/cpp/end2end/generic_end2end_test.cc b/test/cpp/end2end/generic_end2end_test.cc
index 8c4b3cf..c280731 100644
--- a/test/cpp/end2end/generic_end2end_test.cc
+++ b/test/cpp/end2end/generic_end2end_test.cc
@@ -90,8 +90,8 @@
   }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     generic_stub_.reset(new GenericStub(channel));
   }
 
diff --git a/test/cpp/end2end/grpclb_end2end_test.cc b/test/cpp/end2end/grpclb_end2end_test.cc
index 7c64323..50b1383 100644
--- a/test/cpp/end2end/grpclb_end2end_test.cc
+++ b/test/cpp/end2end/grpclb_end2end_test.cc
@@ -30,14 +30,15 @@
 #include <grpcpp/channel.h>
 #include <grpcpp/client_context.h>
 #include <grpcpp/create_channel.h>
+#include <grpcpp/impl/codegen/sync.h>
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
 
+#include "src/core/ext/filters/client_channel/backup_poller.h"
 #include "src/core/ext/filters/client_channel/parse_address.h"
 #include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/ext/filters/client_channel/service_config.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/sockaddr.h"
 #include "src/core/lib/security/credentials/fake/fake_credentials.h"
@@ -85,32 +86,32 @@
 class CountedService : public ServiceType {
  public:
   size_t request_count() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     return request_count_;
   }
 
   size_t response_count() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     return response_count_;
   }
 
   void IncreaseResponseCount() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     ++response_count_;
   }
   void IncreaseRequestCount() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     ++request_count_;
   }
 
   void ResetCounters() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     request_count_ = 0;
     response_count_ = 0;
   }
 
  protected:
-  std::mutex mu_;
+  grpc::internal::Mutex mu_;
 
  private:
   size_t request_count_ = 0;
@@ -148,18 +149,18 @@
   void Shutdown() {}
 
   std::set<grpc::string> clients() {
-    std::unique_lock<std::mutex> lock(clients_mu_);
+    grpc::internal::MutexLock lock(&clients_mu_);
     return clients_;
   }
 
  private:
   void AddClient(const grpc::string& client) {
-    std::unique_lock<std::mutex> lock(clients_mu_);
+    grpc::internal::MutexLock lock(&clients_mu_);
     clients_.insert(client);
   }
 
-  std::mutex mu_;
-  std::mutex clients_mu_;
+  grpc::internal::Mutex mu_;
+  grpc::internal::Mutex clients_mu_;
   std::set<grpc::string> clients_;
 };
 
@@ -208,67 +209,74 @@
             client_load_reporting_interval_seconds) {}
 
   Status BalanceLoad(ServerContext* context, Stream* stream) override {
-    // Balancer shouldn't receive the call credentials metadata.
-    EXPECT_EQ(context->client_metadata().find(g_kCallCredsMdKey),
-              context->client_metadata().end());
     gpr_log(GPR_INFO, "LB[%p]: BalanceLoad", this);
-    LoadBalanceRequest request;
-    std::vector<ResponseDelayPair> responses_and_delays;
-
-    if (!stream->Read(&request)) {
-      goto done;
-    }
-    IncreaseRequestCount();
-    gpr_log(GPR_INFO, "LB[%p]: received initial message '%s'", this,
-            request.DebugString().c_str());
-
-    // TODO(juanlishen): Initial response should always be the first response.
-    if (client_load_reporting_interval_seconds_ > 0) {
-      LoadBalanceResponse initial_response;
-      initial_response.mutable_initial_response()
-          ->mutable_client_stats_report_interval()
-          ->set_seconds(client_load_reporting_interval_seconds_);
-      stream->Write(initial_response);
-    }
-
     {
-      std::unique_lock<std::mutex> lock(mu_);
-      responses_and_delays = responses_and_delays_;
-    }
-    for (const auto& response_and_delay : responses_and_delays) {
-      SendResponse(stream, response_and_delay.first, response_and_delay.second);
+      grpc::internal::MutexLock lock(&mu_);
+      if (serverlist_done_) goto done;
     }
     {
-      std::unique_lock<std::mutex> lock(mu_);
-      serverlist_cond_.wait(lock, [this] { return serverlist_done_; });
-    }
+      // Balancer shouldn't receive the call credentials metadata.
+      EXPECT_EQ(context->client_metadata().find(g_kCallCredsMdKey),
+                context->client_metadata().end());
+      LoadBalanceRequest request;
+      std::vector<ResponseDelayPair> responses_and_delays;
 
-    if (client_load_reporting_interval_seconds_ > 0) {
-      request.Clear();
-      if (stream->Read(&request)) {
-        gpr_log(GPR_INFO, "LB[%p]: received client load report message '%s'",
-                this, request.DebugString().c_str());
-        GPR_ASSERT(request.has_client_stats());
-        // We need to acquire the lock here in order to prevent the notify_one
-        // below from firing before its corresponding wait is executed.
-        std::lock_guard<std::mutex> lock(mu_);
-        client_stats_.num_calls_started +=
-            request.client_stats().num_calls_started();
-        client_stats_.num_calls_finished +=
-            request.client_stats().num_calls_finished();
-        client_stats_.num_calls_finished_with_client_failed_to_send +=
-            request.client_stats()
-                .num_calls_finished_with_client_failed_to_send();
-        client_stats_.num_calls_finished_known_received +=
-            request.client_stats().num_calls_finished_known_received();
-        for (const auto& drop_token_count :
-             request.client_stats().calls_finished_with_drop()) {
-          client_stats_
-              .drop_token_counts[drop_token_count.load_balance_token()] +=
-              drop_token_count.num_calls();
+      if (!stream->Read(&request)) {
+        goto done;
+      }
+      IncreaseRequestCount();
+      gpr_log(GPR_INFO, "LB[%p]: received initial message '%s'", this,
+              request.DebugString().c_str());
+
+      // TODO(juanlishen): Initial response should always be the first response.
+      if (client_load_reporting_interval_seconds_ > 0) {
+        LoadBalanceResponse initial_response;
+        initial_response.mutable_initial_response()
+            ->mutable_client_stats_report_interval()
+            ->set_seconds(client_load_reporting_interval_seconds_);
+        stream->Write(initial_response);
+      }
+
+      {
+        grpc::internal::MutexLock lock(&mu_);
+        responses_and_delays = responses_and_delays_;
+      }
+      for (const auto& response_and_delay : responses_and_delays) {
+        SendResponse(stream, response_and_delay.first,
+                     response_and_delay.second);
+      }
+      {
+        grpc::internal::MutexLock lock(&mu_);
+        serverlist_cond_.WaitUntil(&mu_, [this] { return serverlist_done_; });
+      }
+
+      if (client_load_reporting_interval_seconds_ > 0) {
+        request.Clear();
+        if (stream->Read(&request)) {
+          gpr_log(GPR_INFO, "LB[%p]: received client load report message '%s'",
+                  this, request.DebugString().c_str());
+          GPR_ASSERT(request.has_client_stats());
+          // We need to acquire the lock here in order to prevent the notify_one
+          // below from firing before its corresponding wait is executed.
+          grpc::internal::MutexLock lock(&mu_);
+          client_stats_.num_calls_started +=
+              request.client_stats().num_calls_started();
+          client_stats_.num_calls_finished +=
+              request.client_stats().num_calls_finished();
+          client_stats_.num_calls_finished_with_client_failed_to_send +=
+              request.client_stats()
+                  .num_calls_finished_with_client_failed_to_send();
+          client_stats_.num_calls_finished_known_received +=
+              request.client_stats().num_calls_finished_known_received();
+          for (const auto& drop_token_count :
+               request.client_stats().calls_finished_with_drop()) {
+            client_stats_
+                .drop_token_counts[drop_token_count.load_balance_token()] +=
+                drop_token_count.num_calls();
+          }
+          load_report_ready_ = true;
+          load_report_cond_.Signal();
         }
-        load_report_ready_ = true;
-        load_report_cond_.notify_one();
       }
     }
   done:
@@ -277,12 +285,12 @@
   }
 
   void add_response(const LoadBalanceResponse& response, int send_after_ms) {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     responses_and_delays_.push_back(std::make_pair(response, send_after_ms));
   }
 
   void Start() {
-    std::lock_guard<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     serverlist_done_ = false;
     load_report_ready_ = false;
     responses_and_delays_.clear();
@@ -319,17 +327,17 @@
   }
 
   const ClientStats& WaitForLoadReport() {
-    std::unique_lock<std::mutex> lock(mu_);
-    load_report_cond_.wait(lock, [this] { return load_report_ready_; });
+    grpc::internal::MutexLock lock(&mu_);
+    load_report_cond_.WaitUntil(&mu_, [this] { return load_report_ready_; });
     load_report_ready_ = false;
     return client_stats_;
   }
 
   void NotifyDoneWithServerlists() {
-    std::lock_guard<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     if (!serverlist_done_) {
       serverlist_done_ = true;
-      serverlist_cond_.notify_all();
+      serverlist_cond_.Broadcast();
     }
   }
 
@@ -348,10 +356,10 @@
 
   const int client_load_reporting_interval_seconds_;
   std::vector<ResponseDelayPair> responses_and_delays_;
-  std::mutex mu_;
-  std::condition_variable load_report_cond_;
+  grpc::internal::Mutex mu_;
+  grpc::internal::CondVar load_report_cond_;
   bool load_report_ready_ = false;
-  std::condition_variable serverlist_cond_;
+  grpc::internal::CondVar serverlist_cond_;
   bool serverlist_done_ = false;
   ClientStats client_stats_;
 };
@@ -367,7 +375,7 @@
             client_load_reporting_interval_seconds) {
     // Make the backup poller poll very frequently in order to pick up
     // updates from all the subchannels's FDs.
-    gpr_setenv("GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS", "1");
+    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
   }
 
   void SetUp() override {
@@ -426,7 +434,7 @@
             channel_creds, call_creds, nullptr)));
     call_creds->Unref();
     channel_creds->Unref();
-    channel_ = CreateCustomChannel(uri.str(), creds, args);
+    channel_ = ::grpc::CreateCustomChannel(uri.str(), creds, args);
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
@@ -542,8 +550,10 @@
     grpc_core::Resolver::Result result;
     result.addresses = CreateLbAddressesFromAddressDataList(address_data);
     if (service_config_json != nullptr) {
+      grpc_error* error = GRPC_ERROR_NONE;
       result.service_config =
-          grpc_core::ServiceConfig::Create(service_config_json);
+          grpc_core::ServiceConfig::Create(service_config_json, &error);
+      GRPC_ERROR_UNREF(error);
     }
     response_generator_->SetResponse(std::move(result));
   }
@@ -615,22 +625,22 @@
       GPR_ASSERT(!running_);
       running_ = true;
       service_.Start();
-      std::mutex mu;
+      grpc::internal::Mutex mu;
       // We need to acquire the lock here in order to prevent the notify_one
       // by ServerThread::Serve from firing before the wait below is hit.
-      std::unique_lock<std::mutex> lock(mu);
-      std::condition_variable cond;
+      grpc::internal::MutexLock lock(&mu);
+      grpc::internal::CondVar cond;
       thread_.reset(new std::thread(
           std::bind(&ServerThread::Serve, this, server_host, &mu, &cond)));
-      cond.wait(lock);
+      cond.Wait(&mu);
       gpr_log(GPR_INFO, "%s server startup complete", type_.c_str());
     }
 
-    void Serve(const grpc::string& server_host, std::mutex* mu,
-               std::condition_variable* cond) {
+    void Serve(const grpc::string& server_host, grpc::internal::Mutex* mu,
+               grpc::internal::CondVar* cond) {
       // We need to acquire the lock here in order to prevent the notify_one
       // below from firing before its corresponding wait is executed.
-      std::lock_guard<std::mutex> lock(*mu);
+      grpc::internal::MutexLock lock(mu);
       std::ostringstream server_address;
       server_address << server_host << ":" << port_;
       ServerBuilder builder;
@@ -639,7 +649,7 @@
       builder.AddListeningPort(server_address.str(), creds);
       builder.RegisterService(&service_);
       server_ = builder.BuildAndStart();
-      cond->notify_one();
+      cond->Signal();
     }
 
     void Shutdown() {
@@ -728,6 +738,39 @@
 }
 
 TEST_F(SingleBalancerTest,
+       DoNotSpecialCaseUseGrpclbWithLoadBalancingConfigTest) {
+  const int kFallbackTimeoutMs = 200 * grpc_test_slowdown_factor();
+  ResetStub(kFallbackTimeoutMs);
+  SetNextResolution({AddressData{backends_[0]->port_, false, ""},
+                     AddressData{balancers_[0]->port_, true, ""}},
+                    "{\n"
+                    " \"loadBalancingConfig\":[\n"
+                    "  {\"pick_first\":{} }\n"
+                    " ]\n"
+                    "}");
+  CheckRpcSendOk();
+  // Check LB policy name for the channel.
+  EXPECT_EQ("pick_first", channel_->GetLoadBalancingPolicyName());
+}
+
+TEST_F(
+    SingleBalancerTest,
+    DoNotSpecialCaseUseGrpclbWithLoadBalancingConfigTestAndNoBackendAddress) {
+  const int kFallbackTimeoutMs = 200 * grpc_test_slowdown_factor();
+  ResetStub(kFallbackTimeoutMs);
+  SetNextResolution({AddressData{balancers_[0]->port_, true, ""}},
+                    "{\n"
+                    " \"loadBalancingConfig\":[\n"
+                    "  {\"pick_first\":{} }\n"
+                    " ]\n"
+                    "}");
+  // This should fail since we do not have a non-balancer backend
+  CheckRpcSendFailure();
+  // Check LB policy name for the channel.
+  EXPECT_EQ("pick_first", channel_->GetLoadBalancingPolicyName());
+}
+
+TEST_F(SingleBalancerTest,
        SelectGrpclbWithMigrationServiceConfigAndNoAddresses) {
   const int kFallbackTimeoutMs = 200 * grpc_test_slowdown_factor();
   ResetStub(kFallbackTimeoutMs);
@@ -1024,12 +1067,12 @@
   SetNextResolutionAllBalancers();
   const int kFallbackTimeoutMs = 200 * grpc_test_slowdown_factor();
   const int kServerlistDelayMs = 500 * grpc_test_slowdown_factor();
-  const size_t kNumBackendInResolution = backends_.size() / 2;
+  const size_t kNumBackendsInResolution = backends_.size() / 2;
 
   ResetStub(kFallbackTimeoutMs);
   std::vector<AddressData> addresses;
   addresses.emplace_back(AddressData{balancers_[0]->port_, true, ""});
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     addresses.emplace_back(AddressData{backends_[i]->port_, false, ""});
   }
   SetNextResolution(addresses);
@@ -1038,45 +1081,45 @@
   ScheduleResponseForBalancer(
       0,
       BalancerServiceImpl::BuildResponseForBackends(
-          GetBackendPorts(kNumBackendInResolution /* start_index */), {}),
+          GetBackendPorts(kNumBackendsInResolution /* start_index */), {}),
       kServerlistDelayMs);
 
   // Wait until all the fallback backends are reachable.
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     WaitForBackend(i);
   }
 
   // The first request.
   gpr_log(GPR_INFO, "========= BEFORE FIRST BATCH ==========");
-  CheckRpcSendOk(kNumBackendInResolution);
+  CheckRpcSendOk(kNumBackendsInResolution);
   gpr_log(GPR_INFO, "========= DONE WITH FIRST BATCH ==========");
 
   // Fallback is used: each backend returned by the resolver should have
   // gotten one request.
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     EXPECT_EQ(1U, backends_[i]->service_.request_count());
   }
-  for (size_t i = kNumBackendInResolution; i < backends_.size(); ++i) {
+  for (size_t i = kNumBackendsInResolution; i < backends_.size(); ++i) {
     EXPECT_EQ(0U, backends_[i]->service_.request_count());
   }
 
   // Wait until the serverlist reception has been processed and all backends
   // in the serverlist are reachable.
-  for (size_t i = kNumBackendInResolution; i < backends_.size(); ++i) {
+  for (size_t i = kNumBackendsInResolution; i < backends_.size(); ++i) {
     WaitForBackend(i);
   }
 
   // Send out the second request.
   gpr_log(GPR_INFO, "========= BEFORE SECOND BATCH ==========");
-  CheckRpcSendOk(backends_.size() - kNumBackendInResolution);
+  CheckRpcSendOk(backends_.size() - kNumBackendsInResolution);
   gpr_log(GPR_INFO, "========= DONE WITH SECOND BATCH ==========");
 
   // Serverlist is used: each backend returned by the balancer should
   // have gotten one request.
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     EXPECT_EQ(0U, backends_[i]->service_.request_count());
   }
-  for (size_t i = kNumBackendInResolution; i < backends_.size(); ++i) {
+  for (size_t i = kNumBackendsInResolution; i < backends_.size(); ++i) {
     EXPECT_EQ(1U, backends_[i]->service_.request_count());
   }
 
@@ -1091,13 +1134,13 @@
   SetNextResolutionAllBalancers();
   const int kFallbackTimeoutMs = 200 * grpc_test_slowdown_factor();
   const int kServerlistDelayMs = 500 * grpc_test_slowdown_factor();
-  const size_t kNumBackendInResolution = backends_.size() / 3;
-  const size_t kNumBackendInResolutionUpdate = backends_.size() / 3;
+  const size_t kNumBackendsInResolution = backends_.size() / 3;
+  const size_t kNumBackendsInResolutionUpdate = backends_.size() / 3;
 
   ResetStub(kFallbackTimeoutMs);
   std::vector<AddressData> addresses;
   addresses.emplace_back(AddressData{balancers_[0]->port_, true, ""});
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     addresses.emplace_back(AddressData{backends_[i]->port_, false, ""});
   }
   SetNextResolution(addresses);
@@ -1106,84 +1149,84 @@
   ScheduleResponseForBalancer(
       0,
       BalancerServiceImpl::BuildResponseForBackends(
-          GetBackendPorts(kNumBackendInResolution +
-                          kNumBackendInResolutionUpdate /* start_index */),
+          GetBackendPorts(kNumBackendsInResolution +
+                          kNumBackendsInResolutionUpdate /* start_index */),
           {}),
       kServerlistDelayMs);
 
   // Wait until all the fallback backends are reachable.
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     WaitForBackend(i);
   }
 
   // The first request.
   gpr_log(GPR_INFO, "========= BEFORE FIRST BATCH ==========");
-  CheckRpcSendOk(kNumBackendInResolution);
+  CheckRpcSendOk(kNumBackendsInResolution);
   gpr_log(GPR_INFO, "========= DONE WITH FIRST BATCH ==========");
 
   // Fallback is used: each backend returned by the resolver should have
   // gotten one request.
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     EXPECT_EQ(1U, backends_[i]->service_.request_count());
   }
-  for (size_t i = kNumBackendInResolution; i < backends_.size(); ++i) {
+  for (size_t i = kNumBackendsInResolution; i < backends_.size(); ++i) {
     EXPECT_EQ(0U, backends_[i]->service_.request_count());
   }
 
   addresses.clear();
   addresses.emplace_back(AddressData{balancers_[0]->port_, true, ""});
-  for (size_t i = kNumBackendInResolution;
-       i < kNumBackendInResolution + kNumBackendInResolutionUpdate; ++i) {
+  for (size_t i = kNumBackendsInResolution;
+       i < kNumBackendsInResolution + kNumBackendsInResolutionUpdate; ++i) {
     addresses.emplace_back(AddressData{backends_[i]->port_, false, ""});
   }
   SetNextResolution(addresses);
 
   // Wait until the resolution update has been processed and all the new
   // fallback backends are reachable.
-  for (size_t i = kNumBackendInResolution;
-       i < kNumBackendInResolution + kNumBackendInResolutionUpdate; ++i) {
+  for (size_t i = kNumBackendsInResolution;
+       i < kNumBackendsInResolution + kNumBackendsInResolutionUpdate; ++i) {
     WaitForBackend(i);
   }
 
   // Send out the second request.
   gpr_log(GPR_INFO, "========= BEFORE SECOND BATCH ==========");
-  CheckRpcSendOk(kNumBackendInResolutionUpdate);
+  CheckRpcSendOk(kNumBackendsInResolutionUpdate);
   gpr_log(GPR_INFO, "========= DONE WITH SECOND BATCH ==========");
 
   // The resolution update is used: each backend in the resolution update should
   // have gotten one request.
-  for (size_t i = 0; i < kNumBackendInResolution; ++i) {
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
     EXPECT_EQ(0U, backends_[i]->service_.request_count());
   }
-  for (size_t i = kNumBackendInResolution;
-       i < kNumBackendInResolution + kNumBackendInResolutionUpdate; ++i) {
+  for (size_t i = kNumBackendsInResolution;
+       i < kNumBackendsInResolution + kNumBackendsInResolutionUpdate; ++i) {
     EXPECT_EQ(1U, backends_[i]->service_.request_count());
   }
-  for (size_t i = kNumBackendInResolution + kNumBackendInResolutionUpdate;
+  for (size_t i = kNumBackendsInResolution + kNumBackendsInResolutionUpdate;
        i < backends_.size(); ++i) {
     EXPECT_EQ(0U, backends_[i]->service_.request_count());
   }
 
   // Wait until the serverlist reception has been processed and all backends
   // in the serverlist are reachable.
-  for (size_t i = kNumBackendInResolution + kNumBackendInResolutionUpdate;
+  for (size_t i = kNumBackendsInResolution + kNumBackendsInResolutionUpdate;
        i < backends_.size(); ++i) {
     WaitForBackend(i);
   }
 
   // Send out the third request.
   gpr_log(GPR_INFO, "========= BEFORE THIRD BATCH ==========");
-  CheckRpcSendOk(backends_.size() - kNumBackendInResolution -
-                 kNumBackendInResolutionUpdate);
+  CheckRpcSendOk(backends_.size() - kNumBackendsInResolution -
+                 kNumBackendsInResolutionUpdate);
   gpr_log(GPR_INFO, "========= DONE WITH THIRD BATCH ==========");
 
   // Serverlist is used: each backend returned by the balancer should
   // have gotten one request.
   for (size_t i = 0;
-       i < kNumBackendInResolution + kNumBackendInResolutionUpdate; ++i) {
+       i < kNumBackendsInResolution + kNumBackendsInResolutionUpdate; ++i) {
     EXPECT_EQ(0U, backends_[i]->service_.request_count());
   }
-  for (size_t i = kNumBackendInResolution + kNumBackendInResolutionUpdate;
+  for (size_t i = kNumBackendsInResolution + kNumBackendsInResolutionUpdate;
        i < backends_.size(); ++i) {
     EXPECT_EQ(1U, backends_[i]->service_.request_count());
   }
@@ -1359,7 +1402,7 @@
   UpdatesTest() : GrpclbEnd2endTest(4, 3, 0) {}
 };
 
-TEST_F(UpdatesTest, UpdateBalancers) {
+TEST_F(UpdatesTest, UpdateBalancersButKeepUsingOriginalBalancer) {
   SetNextResolutionAllBalancers();
   const std::vector<int> first_backend{GetBackendPorts()[0]};
   const std::vector<int> second_backend{GetBackendPorts()[1]};
@@ -1379,9 +1422,6 @@
   // All 10 requests should have gone to the first backend.
   EXPECT_EQ(10U, backends_[0]->service_.request_count());
 
-  balancers_[0]->service_.NotifyDoneWithServerlists();
-  balancers_[1]->service_.NotifyDoneWithServerlists();
-  balancers_[2]->service_.NotifyDoneWithServerlists();
   // Balancer 0 got a single request.
   EXPECT_EQ(1U, balancers_[0]->service_.request_count());
   // and sent a single response.
@@ -1397,25 +1437,21 @@
   SetNextResolution(addresses);
   gpr_log(GPR_INFO, "========= UPDATE 1 DONE ==========");
 
-  // Wait until update has been processed, as signaled by the second backend
-  // receiving a request.
   EXPECT_EQ(0U, backends_[1]->service_.request_count());
-  WaitForBackend(1);
+  gpr_timespec deadline = gpr_time_add(
+      gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_millis(10000, GPR_TIMESPAN));
+  // Send 10 seconds worth of RPCs
+  do {
+    CheckRpcSendOk();
+  } while (gpr_time_cmp(gpr_now(GPR_CLOCK_REALTIME), deadline) < 0);
+  // The current LB call is still working, so grpclb continued using it to the
+  // first balancer, which doesn't assign the second backend.
+  EXPECT_EQ(0U, backends_[1]->service_.request_count());
 
-  backends_[1]->service_.ResetCounters();
-  gpr_log(GPR_INFO, "========= BEFORE SECOND BATCH ==========");
-  CheckRpcSendOk(10);
-  gpr_log(GPR_INFO, "========= DONE WITH SECOND BATCH ==========");
-  // All 10 requests should have gone to the second backend.
-  EXPECT_EQ(10U, backends_[1]->service_.request_count());
-
-  balancers_[0]->service_.NotifyDoneWithServerlists();
-  balancers_[1]->service_.NotifyDoneWithServerlists();
-  balancers_[2]->service_.NotifyDoneWithServerlists();
   EXPECT_EQ(1U, balancers_[0]->service_.request_count());
   EXPECT_EQ(1U, balancers_[0]->service_.response_count());
-  EXPECT_EQ(1U, balancers_[1]->service_.request_count());
-  EXPECT_EQ(1U, balancers_[1]->service_.response_count());
+  EXPECT_EQ(0U, balancers_[1]->service_.request_count());
+  EXPECT_EQ(0U, balancers_[1]->service_.response_count());
   EXPECT_EQ(0U, balancers_[2]->service_.request_count());
   EXPECT_EQ(0U, balancers_[2]->service_.response_count());
 }
@@ -1526,9 +1562,6 @@
   EXPECT_EQ(20U, backends_[0]->service_.request_count());
   EXPECT_EQ(0U, backends_[1]->service_.request_count());
 
-  balancers_[0]->service_.NotifyDoneWithServerlists();
-  balancers_[1]->service_.NotifyDoneWithServerlists();
-  balancers_[2]->service_.NotifyDoneWithServerlists();
   // Balancer 0 got a single request.
   EXPECT_EQ(1U, balancers_[0]->service_.request_count());
   // and sent a single response.
@@ -1558,9 +1591,6 @@
   // All 10 requests should have gone to the second backend.
   EXPECT_EQ(10U, backends_[1]->service_.request_count());
 
-  balancers_[0]->service_.NotifyDoneWithServerlists();
-  balancers_[1]->service_.NotifyDoneWithServerlists();
-  balancers_[2]->service_.NotifyDoneWithServerlists();
   EXPECT_EQ(1U, balancers_[0]->service_.request_count());
   EXPECT_EQ(1U, balancers_[0]->service_.response_count());
   // The second balancer, published as part of the first update, may end up
diff --git a/test/cpp/end2end/health_service_end2end_test.cc b/test/cpp/end2end/health_service_end2end_test.cc
index b96ff53..13d5ea5 100644
--- a/test/cpp/end2end/health_service_end2end_test.cc
+++ b/test/cpp/end2end/health_service_end2end_test.cc
@@ -124,8 +124,8 @@
   }
 
   void ResetStubs() {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     hc_stub_ = grpc::health::v1::Health::NewStub(channel);
   }
 
diff --git a/test/cpp/end2end/hybrid_end2end_test.cc b/test/cpp/end2end/hybrid_end2end_test.cc
index b0dd901..75001f0 100644
--- a/test/cpp/end2end/hybrid_end2end_test.cc
+++ b/test/cpp/end2end/hybrid_end2end_test.cc
@@ -296,8 +296,8 @@
   void ResetStub() {
     std::shared_ptr<Channel> channel =
         inproc_ ? server_->InProcessChannel(ChannelArguments())
-                : CreateChannel(server_address_.str(),
-                                InsecureChannelCredentials());
+                : grpc::CreateChannel(server_address_.str(),
+                                      InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
 
@@ -321,8 +321,8 @@
   }
 
   void SendEchoToDupService() {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     auto stub = grpc::testing::duplicate::EchoTestService::NewStub(channel);
     EchoRequest send_request;
     EchoResponse recv_response;
@@ -373,8 +373,8 @@
   }
 
   void SendSimpleServerStreamingToDupService() {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     auto stub = grpc::testing::duplicate::EchoTestService::NewStub(channel);
     EchoRequest request;
     EchoResponse response;
diff --git a/test/cpp/end2end/message_allocator_end2end_test.cc b/test/cpp/end2end/message_allocator_end2end_test.cc
new file mode 100644
index 0000000..c833a4f
--- /dev/null
+++ b/test/cpp/end2end/message_allocator_end2end_test.cc
@@ -0,0 +1,405 @@
+/*
+ *
+ * Copyright 2019 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 <algorithm>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <sstream>
+#include <thread>
+
+#include <google/protobuf/arena.h>
+
+#include <gtest/gtest.h>
+
+#include <grpcpp/channel.h>
+#include <grpcpp/client_context.h>
+#include <grpcpp/create_channel.h>
+#include <grpcpp/server.h>
+#include <grpcpp/server_builder.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/client_callback.h>
+#include <grpcpp/support/message_allocator.h>
+
+#include "src/core/lib/iomgr/iomgr.h"
+#include "src/proto/grpc/testing/echo.grpc.pb.h"
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+#include "test/cpp/util/test_credentials_provider.h"
+
+// MAYBE_SKIP_TEST is a macro to determine if this particular test configuration
+// should be skipped based on a decision made at SetUp time. In particular, any
+// callback tests can only be run if the iomgr can run in the background or if
+// the transport is in-process.
+#define MAYBE_SKIP_TEST \
+  do {                  \
+    if (do_not_test_) { \
+      return;           \
+    }                   \
+  } while (0)
+
+namespace grpc {
+namespace testing {
+namespace {
+
+class CallbackTestServiceImpl
+    : public EchoTestService::ExperimentalCallbackService {
+ public:
+  explicit CallbackTestServiceImpl() {}
+
+  void SetFreeRequest() { free_request_ = true; }
+
+  void SetAllocatorMutator(
+      std::function<void(void* allocator_state, const EchoRequest* req,
+                         EchoResponse* resp)>
+          mutator) {
+    allocator_mutator_ = mutator;
+  }
+
+  void Echo(ServerContext* context, const EchoRequest* request,
+            EchoResponse* response,
+            experimental::ServerCallbackRpcController* controller) override {
+    response->set_message(request->message());
+    if (free_request_) {
+      controller->FreeRequest();
+    } else if (allocator_mutator_) {
+      allocator_mutator_(controller->GetAllocatorState(), request, response);
+    }
+    controller->Finish(Status::OK);
+  }
+
+ private:
+  bool free_request_ = false;
+  std::function<void(void* allocator_state, const EchoRequest* req,
+                     EchoResponse* resp)>
+      allocator_mutator_;
+};
+
+enum class Protocol { INPROC, TCP };
+
+class TestScenario {
+ public:
+  TestScenario(Protocol protocol, const grpc::string& creds_type)
+      : protocol(protocol), credentials_type(creds_type) {}
+  void Log() const;
+  Protocol protocol;
+  const grpc::string credentials_type;
+};
+
+static std::ostream& operator<<(std::ostream& out,
+                                const TestScenario& scenario) {
+  return out << "TestScenario{protocol="
+             << (scenario.protocol == Protocol::INPROC ? "INPROC" : "TCP")
+             << "," << scenario.credentials_type << "}";
+}
+
+void TestScenario::Log() const {
+  std::ostringstream out;
+  out << *this;
+  gpr_log(GPR_INFO, "%s", out.str().c_str());
+}
+
+class MessageAllocatorEnd2endTestBase
+    : public ::testing::TestWithParam<TestScenario> {
+ protected:
+  MessageAllocatorEnd2endTestBase() {
+    GetParam().Log();
+    if (GetParam().protocol == Protocol::TCP) {
+      if (!grpc_iomgr_run_in_background()) {
+        do_not_test_ = true;
+        return;
+      }
+    }
+  }
+
+  ~MessageAllocatorEnd2endTestBase() = default;
+
+  void CreateServer(
+      experimental::MessageAllocator<EchoRequest, EchoResponse>* allocator) {
+    ServerBuilder builder;
+
+    auto server_creds = GetCredentialsProvider()->GetServerCredentials(
+        GetParam().credentials_type);
+    if (GetParam().protocol == Protocol::TCP) {
+      picked_port_ = grpc_pick_unused_port_or_die();
+      server_address_ << "localhost:" << picked_port_;
+      builder.AddListeningPort(server_address_.str(), server_creds);
+    }
+    callback_service_.SetMessageAllocatorFor_Echo(allocator);
+    builder.RegisterService(&callback_service_);
+
+    server_ = builder.BuildAndStart();
+    is_server_started_ = true;
+  }
+
+  void ResetStub() {
+    ChannelArguments args;
+    auto channel_creds = GetCredentialsProvider()->GetChannelCredentials(
+        GetParam().credentials_type, &args);
+    switch (GetParam().protocol) {
+      case Protocol::TCP:
+        channel_ = ::grpc::CreateCustomChannel(server_address_.str(),
+                                               channel_creds, args);
+        break;
+      case Protocol::INPROC:
+        channel_ = server_->InProcessChannel(args);
+        break;
+      default:
+        assert(false);
+    }
+    stub_ = EchoTestService::NewStub(channel_);
+  }
+
+  void TearDown() override {
+    if (is_server_started_) {
+      server_->Shutdown();
+    }
+    if (picked_port_ > 0) {
+      grpc_recycle_unused_port(picked_port_);
+    }
+  }
+
+  void SendRpcs(int num_rpcs) {
+    grpc::string test_string("");
+    for (int i = 0; i < num_rpcs; i++) {
+      EchoRequest request;
+      EchoResponse response;
+      ClientContext cli_ctx;
+
+      test_string += grpc::string(1024, 'x');
+      request.set_message(test_string);
+      grpc::string val;
+      cli_ctx.set_compression_algorithm(GRPC_COMPRESS_GZIP);
+
+      std::mutex mu;
+      std::condition_variable cv;
+      bool done = false;
+      stub_->experimental_async()->Echo(
+          &cli_ctx, &request, &response,
+          [&request, &response, &done, &mu, &cv, val](Status s) {
+            GPR_ASSERT(s.ok());
+
+            EXPECT_EQ(request.message(), response.message());
+            std::lock_guard<std::mutex> l(mu);
+            done = true;
+            cv.notify_one();
+          });
+      std::unique_lock<std::mutex> l(mu);
+      while (!done) {
+        cv.wait(l);
+      }
+    }
+  }
+
+  bool do_not_test_{false};
+  bool is_server_started_{false};
+  int picked_port_{0};
+  std::shared_ptr<Channel> channel_;
+  std::unique_ptr<EchoTestService::Stub> stub_;
+  CallbackTestServiceImpl callback_service_;
+  std::unique_ptr<Server> server_;
+  std::ostringstream server_address_;
+};
+
+class NullAllocatorTest : public MessageAllocatorEnd2endTestBase {};
+
+TEST_P(NullAllocatorTest, SimpleRpc) {
+  MAYBE_SKIP_TEST;
+  CreateServer(nullptr);
+  ResetStub();
+  SendRpcs(1);
+}
+
+class SimpleAllocatorTest : public MessageAllocatorEnd2endTestBase {
+ public:
+  class SimpleAllocator
+      : public experimental::MessageAllocator<EchoRequest, EchoResponse> {
+   public:
+    void AllocateMessages(
+        experimental::RpcAllocatorInfo<EchoRequest, EchoResponse>* info) {
+      allocation_count++;
+      info->request = new EchoRequest;
+      info->response = new EchoResponse;
+      info->allocator_state = info;
+    }
+    void DeallocateRequest(
+        experimental::RpcAllocatorInfo<EchoRequest, EchoResponse>* info) {
+      request_deallocation_count++;
+      delete info->request;
+      info->request = nullptr;
+    }
+    void DeallocateMessages(
+        experimental::RpcAllocatorInfo<EchoRequest, EchoResponse>* info) {
+      messages_deallocation_count++;
+      delete info->request;
+      delete info->response;
+    }
+
+    int allocation_count = 0;
+    int request_deallocation_count = 0;
+    int messages_deallocation_count = 0;
+  };
+};
+
+TEST_P(SimpleAllocatorTest, SimpleRpc) {
+  MAYBE_SKIP_TEST;
+  const int kRpcCount = 10;
+  std::unique_ptr<SimpleAllocator> allocator(new SimpleAllocator);
+  CreateServer(allocator.get());
+  ResetStub();
+  SendRpcs(kRpcCount);
+  EXPECT_EQ(kRpcCount, allocator->allocation_count);
+  EXPECT_EQ(kRpcCount, allocator->messages_deallocation_count);
+  EXPECT_EQ(0, allocator->request_deallocation_count);
+}
+
+TEST_P(SimpleAllocatorTest, RpcWithEarlyFreeRequest) {
+  MAYBE_SKIP_TEST;
+  const int kRpcCount = 10;
+  std::unique_ptr<SimpleAllocator> allocator(new SimpleAllocator);
+  callback_service_.SetFreeRequest();
+  CreateServer(allocator.get());
+  ResetStub();
+  SendRpcs(kRpcCount);
+  EXPECT_EQ(kRpcCount, allocator->allocation_count);
+  EXPECT_EQ(kRpcCount, allocator->messages_deallocation_count);
+  EXPECT_EQ(kRpcCount, allocator->request_deallocation_count);
+}
+
+TEST_P(SimpleAllocatorTest, RpcWithReleaseRequest) {
+  MAYBE_SKIP_TEST;
+  const int kRpcCount = 10;
+  std::unique_ptr<SimpleAllocator> allocator(new SimpleAllocator);
+  std::vector<EchoRequest*> released_requests;
+  auto mutator = [&released_requests](void* allocator_state,
+                                      const EchoRequest* req,
+                                      EchoResponse* resp) {
+    auto* info =
+        static_cast<experimental::RpcAllocatorInfo<EchoRequest, EchoResponse>*>(
+            allocator_state);
+    EXPECT_EQ(req, info->request);
+    EXPECT_EQ(resp, info->response);
+    EXPECT_EQ(allocator_state, info->allocator_state);
+    released_requests.push_back(info->request);
+    info->request = nullptr;
+  };
+  callback_service_.SetAllocatorMutator(mutator);
+  CreateServer(allocator.get());
+  ResetStub();
+  SendRpcs(kRpcCount);
+  EXPECT_EQ(kRpcCount, allocator->allocation_count);
+  EXPECT_EQ(kRpcCount, allocator->messages_deallocation_count);
+  EXPECT_EQ(0, allocator->request_deallocation_count);
+  EXPECT_EQ(static_cast<unsigned>(kRpcCount), released_requests.size());
+  for (auto* req : released_requests) {
+    delete req;
+  }
+}
+
+class ArenaAllocatorTest : public MessageAllocatorEnd2endTestBase {
+ public:
+  class ArenaAllocator
+      : public experimental::MessageAllocator<EchoRequest, EchoResponse> {
+   public:
+    void AllocateMessages(
+        experimental::RpcAllocatorInfo<EchoRequest, EchoResponse>* info) {
+      allocation_count++;
+      auto* arena = new google::protobuf::Arena;
+      info->allocator_state = arena;
+      info->request =
+          google::protobuf::Arena::CreateMessage<EchoRequest>(arena);
+      info->response =
+          google::protobuf::Arena::CreateMessage<EchoResponse>(arena);
+    }
+    void DeallocateRequest(
+        experimental::RpcAllocatorInfo<EchoRequest, EchoResponse>* info) {
+      GPR_ASSERT(0);
+    }
+    void DeallocateMessages(
+        experimental::RpcAllocatorInfo<EchoRequest, EchoResponse>* info) {
+      deallocation_count++;
+      auto* arena =
+          static_cast<google::protobuf::Arena*>(info->allocator_state);
+      delete arena;
+    }
+
+    int allocation_count = 0;
+    int deallocation_count = 0;
+  };
+};
+
+TEST_P(ArenaAllocatorTest, SimpleRpc) {
+  MAYBE_SKIP_TEST;
+  const int kRpcCount = 10;
+  std::unique_ptr<ArenaAllocator> allocator(new ArenaAllocator);
+  CreateServer(allocator.get());
+  ResetStub();
+  SendRpcs(kRpcCount);
+  EXPECT_EQ(kRpcCount, allocator->allocation_count);
+  EXPECT_EQ(kRpcCount, allocator->deallocation_count);
+}
+
+std::vector<TestScenario> CreateTestScenarios(bool test_insecure) {
+  std::vector<TestScenario> scenarios;
+  std::vector<grpc::string> credentials_types{
+      GetCredentialsProvider()->GetSecureCredentialsTypeList()};
+  auto insec_ok = [] {
+    // Only allow insecure credentials type when it is registered with the
+    // provider. User may create providers that do not have insecure.
+    return GetCredentialsProvider()->GetChannelCredentials(
+               kInsecureCredentialsType, nullptr) != nullptr;
+  };
+  if (test_insecure && insec_ok()) {
+    credentials_types.push_back(kInsecureCredentialsType);
+  }
+  GPR_ASSERT(!credentials_types.empty());
+
+  Protocol parr[]{Protocol::INPROC, Protocol::TCP};
+  for (Protocol p : parr) {
+    for (const auto& cred : credentials_types) {
+      // TODO(vjpai): Test inproc with secure credentials when feasible
+      if (p == Protocol::INPROC &&
+          (cred != kInsecureCredentialsType || !insec_ok())) {
+        continue;
+      }
+      scenarios.emplace_back(p, cred);
+    }
+  }
+  return scenarios;
+}
+
+INSTANTIATE_TEST_CASE_P(NullAllocatorTest, NullAllocatorTest,
+                        ::testing::ValuesIn(CreateTestScenarios(true)));
+INSTANTIATE_TEST_CASE_P(SimpleAllocatorTest, SimpleAllocatorTest,
+                        ::testing::ValuesIn(CreateTestScenarios(true)));
+INSTANTIATE_TEST_CASE_P(ArenaAllocatorTest, ArenaAllocatorTest,
+                        ::testing::ValuesIn(CreateTestScenarios(true)));
+
+}  // namespace
+}  // namespace testing
+}  // namespace grpc
+
+int main(int argc, char** argv) {
+  grpc::testing::TestEnvironment env(argc, argv);
+  // The grpc_init is to cover the MAYBE_SKIP_TEST.
+  grpc_init();
+  ::testing::InitGoogleTest(&argc, argv);
+  int ret = RUN_ALL_TESTS();
+  grpc_shutdown();
+  return ret;
+}
diff --git a/test/cpp/end2end/mock_test.cc b/test/cpp/end2end/mock_test.cc
index 917ca28..0196c9d 100644
--- a/test/cpp/end2end/mock_test.cc
+++ b/test/cpp/end2end/mock_test.cc
@@ -244,8 +244,8 @@
   void TearDown() override { server_->Shutdown(); }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
 
diff --git a/test/cpp/end2end/nonblocking_test.cc b/test/cpp/end2end/nonblocking_test.cc
index 36dea1f..eb651df 100644
--- a/test/cpp/end2end/nonblocking_test.cc
+++ b/test/cpp/end2end/nonblocking_test.cc
@@ -106,7 +106,7 @@
   }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel = CreateChannel(
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
         server_address_.str(), grpc::InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
diff --git a/test/cpp/end2end/proto_server_reflection_test.cc b/test/cpp/end2end/proto_server_reflection_test.cc
index ff097aa..d817438 100644
--- a/test/cpp/end2end/proto_server_reflection_test.cc
+++ b/test/cpp/end2end/proto_server_reflection_test.cc
@@ -55,7 +55,7 @@
   void ResetStub() {
     string target = "dns:localhost:" + to_string(port_);
     std::shared_ptr<Channel> channel =
-        CreateChannel(target, InsecureChannelCredentials());
+        grpc::CreateChannel(target, InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
     desc_db_.reset(new ProtoReflectionDescriptorDatabase(channel));
     desc_pool_.reset(new protobuf::DescriptorPool(desc_db_.get()));
diff --git a/test/cpp/end2end/raw_end2end_test.cc b/test/cpp/end2end/raw_end2end_test.cc
index c8556ba..184dc1e 100644
--- a/test/cpp/end2end/raw_end2end_test.cc
+++ b/test/cpp/end2end/raw_end2end_test.cc
@@ -130,7 +130,7 @@
 
   void ResetStub() {
     ChannelArguments args;
-    std::shared_ptr<Channel> channel = CreateChannel(
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
         server_address_.str(), grpc::InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
diff --git a/test/cpp/end2end/server_builder_plugin_test.cc b/test/cpp/end2end/server_builder_plugin_test.cc
index d744a93..43b00b9 100644
--- a/test/cpp/end2end/server_builder_plugin_test.cc
+++ b/test/cpp/end2end/server_builder_plugin_test.cc
@@ -185,7 +185,7 @@
 
   void ResetStub() {
     string target = "dns:localhost:" + to_string(port_);
-    channel_ = CreateChannel(target, InsecureChannelCredentials());
+    channel_ = grpc::CreateChannel(target, InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
diff --git a/test/cpp/end2end/server_crash_test_client.cc b/test/cpp/end2end/server_crash_test_client.cc
index c05fcfd..b261560 100644
--- a/test/cpp/end2end/server_crash_test_client.cc
+++ b/test/cpp/end2end/server_crash_test_client.cc
@@ -28,6 +28,7 @@
 #include <grpcpp/create_channel.h>
 
 #include "src/proto/grpc/testing/echo.grpc.pb.h"
+#include "test/cpp/util/test_config.h"
 
 DEFINE_string(address, "", "Address to connect to");
 DEFINE_string(mode, "", "Test mode to use");
@@ -35,15 +36,8 @@
 using grpc::testing::EchoRequest;
 using grpc::testing::EchoResponse;
 
-// In some distros, gflags is in the namespace google, and in some others,
-// in gflags. This hack is enabling us to find both.
-namespace google {}
-namespace gflags {}
-using namespace google;
-using namespace gflags;
-
 int main(int argc, char** argv) {
-  ParseCommandLineFlags(&argc, &argv, true);
+  grpc::testing::InitTest(&argc, &argv, true);
   auto stub = grpc::testing::EchoTestService::NewStub(
       grpc::CreateChannel(FLAGS_address, grpc::InsecureChannelCredentials()));
 
diff --git a/test/cpp/end2end/server_early_return_test.cc b/test/cpp/end2end/server_early_return_test.cc
index c47e250..6f35c3e 100644
--- a/test/cpp/end2end/server_early_return_test.cc
+++ b/test/cpp/end2end/server_early_return_test.cc
@@ -122,8 +122,8 @@
     builder.RegisterService(&service_);
     server_ = builder.BuildAndStart();
 
-    channel_ =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    channel_ = grpc::CreateChannel(server_address_.str(),
+                                   InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
diff --git a/test/cpp/end2end/server_interceptors_end2end_test.cc b/test/cpp/end2end/server_interceptors_end2end_test.cc
index 028191c..68103f7 100644
--- a/test/cpp/end2end/server_interceptors_end2end_test.cc
+++ b/test/cpp/end2end/server_interceptors_end2end_test.cc
@@ -265,7 +265,8 @@
 TEST_F(ServerInterceptorsEnd2endSyncUnaryTest, UnaryTest) {
   ChannelArguments args;
   DummyInterceptor::Reset();
-  auto channel = CreateChannel(server_address_, InsecureChannelCredentials());
+  auto channel =
+      grpc::CreateChannel(server_address_, InsecureChannelCredentials());
   MakeCall(channel);
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
@@ -308,7 +309,8 @@
 TEST_F(ServerInterceptorsEnd2endSyncStreamingTest, ClientStreamingTest) {
   ChannelArguments args;
   DummyInterceptor::Reset();
-  auto channel = CreateChannel(server_address_, InsecureChannelCredentials());
+  auto channel =
+      grpc::CreateChannel(server_address_, InsecureChannelCredentials());
   MakeClientStreamingCall(channel);
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
@@ -317,7 +319,8 @@
 TEST_F(ServerInterceptorsEnd2endSyncStreamingTest, ServerStreamingTest) {
   ChannelArguments args;
   DummyInterceptor::Reset();
-  auto channel = CreateChannel(server_address_, InsecureChannelCredentials());
+  auto channel =
+      grpc::CreateChannel(server_address_, InsecureChannelCredentials());
   MakeServerStreamingCall(channel);
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
@@ -326,7 +329,8 @@
 TEST_F(ServerInterceptorsEnd2endSyncStreamingTest, BidiStreamingTest) {
   ChannelArguments args;
   DummyInterceptor::Reset();
-  auto channel = CreateChannel(server_address_, InsecureChannelCredentials());
+  auto channel =
+      grpc::CreateChannel(server_address_, InsecureChannelCredentials());
   MakeBidiStreamingCall(channel);
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
@@ -356,7 +360,8 @@
   auto server = builder.BuildAndStart();
 
   ChannelArguments args;
-  auto channel = CreateChannel(server_address, InsecureChannelCredentials());
+  auto channel =
+      grpc::CreateChannel(server_address, InsecureChannelCredentials());
   auto stub = grpc::testing::EchoTestService::NewStub(channel);
 
   EchoRequest send_request;
@@ -428,7 +433,8 @@
   auto server = builder.BuildAndStart();
 
   ChannelArguments args;
-  auto channel = CreateChannel(server_address, InsecureChannelCredentials());
+  auto channel =
+      grpc::CreateChannel(server_address, InsecureChannelCredentials());
   auto stub = grpc::testing::EchoTestService::NewStub(channel);
 
   EchoRequest send_request;
@@ -509,7 +515,8 @@
   auto server = builder.BuildAndStart();
 
   ChannelArguments args;
-  auto channel = CreateChannel(server_address, InsecureChannelCredentials());
+  auto channel =
+      grpc::CreateChannel(server_address, InsecureChannelCredentials());
   GenericStub generic_stub(channel);
 
   const grpc::string kMethodName("/grpc.cpp.test.util.EchoTestService/Echo");
@@ -612,7 +619,7 @@
 
   ChannelArguments args;
   std::shared_ptr<Channel> channel =
-      CreateChannel(server_address, InsecureChannelCredentials());
+      grpc::CreateChannel(server_address, InsecureChannelCredentials());
   std::unique_ptr<grpc::testing::UnimplementedEchoService::Stub> stub;
   stub = grpc::testing::UnimplementedEchoService::NewStub(channel);
   EchoRequest send_request;
@@ -665,7 +672,7 @@
 
   ChannelArguments args;
   std::shared_ptr<Channel> channel =
-      CreateChannel(server_address, InsecureChannelCredentials());
+      grpc::CreateChannel(server_address, InsecureChannelCredentials());
   std::unique_ptr<grpc::testing::UnimplementedEchoService::Stub> stub;
   stub = grpc::testing::UnimplementedEchoService::NewStub(channel);
   EchoRequest send_request;
diff --git a/test/cpp/end2end/server_load_reporting_end2end_test.cc b/test/cpp/end2end/server_load_reporting_end2end_test.cc
index 7bc9af2..8eba912 100644
--- a/test/cpp/end2end/server_load_reporting_end2end_test.cc
+++ b/test/cpp/end2end/server_load_reporting_end2end_test.cc
@@ -94,7 +94,7 @@
                            const grpc::string& lb_tag,
                            const grpc::string& message, size_t num_requests) {
     auto stub = EchoTestService::NewStub(
-        CreateChannel(server_address_, InsecureChannelCredentials()));
+        grpc::CreateChannel(server_address_, InsecureChannelCredentials()));
     grpc::string lb_token = lb_id + lb_tag;
     for (int i = 0; i < num_requests; ++i) {
       ClientContext ctx;
diff --git a/test/cpp/end2end/service_config_end2end_test.cc b/test/cpp/end2end/service_config_end2end_test.cc
new file mode 100644
index 0000000..2d05fd4
--- /dev/null
+++ b/test/cpp/end2end/service_config_end2end_test.cc
@@ -0,0 +1,561 @@
+/*
+ *
+ * Copyright 2016 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 <algorithm>
+#include <memory>
+#include <mutex>
+#include <random>
+#include <set>
+#include <thread>
+
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/atm.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+#include <grpc/support/time.h>
+#include <grpcpp/channel.h>
+#include <grpcpp/client_context.h>
+#include <grpcpp/create_channel.h>
+#include <grpcpp/health_check_service_interface.h>
+#include <grpcpp/impl/codegen/sync.h>
+#include <grpcpp/server.h>
+#include <grpcpp/server_builder.h>
+
+#include "src/core/ext/filters/client_channel/backup_poller.h"
+#include "src/core/ext/filters/client_channel/global_subchannel_pool.h"
+#include "src/core/ext/filters/client_channel/parse_address.h"
+#include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
+#include "src/core/ext/filters/client_channel/server_address.h"
+#include "src/core/lib/backoff/backoff.h"
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gprpp/debug_location.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/iomgr/tcp_client.h"
+#include "src/core/lib/security/credentials/fake/fake_credentials.h"
+#include "src/cpp/client/secure_credentials.h"
+#include "src/cpp/server/secure_server_credentials.h"
+
+#include "src/proto/grpc/testing/echo.grpc.pb.h"
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+#include "test/cpp/end2end/test_service_impl.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using grpc::testing::EchoRequest;
+using grpc::testing::EchoResponse;
+using std::chrono::system_clock;
+
+namespace grpc {
+namespace testing {
+namespace {
+
+// Subclass of TestServiceImpl that increments a request counter for
+// every call to the Echo RPC.
+class MyTestServiceImpl : public TestServiceImpl {
+ public:
+  MyTestServiceImpl() : request_count_(0) {}
+
+  Status Echo(ServerContext* context, const EchoRequest* request,
+              EchoResponse* response) override {
+    {
+      grpc::internal::MutexLock lock(&mu_);
+      ++request_count_;
+    }
+    AddClient(context->peer());
+    return TestServiceImpl::Echo(context, request, response);
+  }
+
+  int request_count() {
+    grpc::internal::MutexLock lock(&mu_);
+    return request_count_;
+  }
+
+  void ResetCounters() {
+    grpc::internal::MutexLock lock(&mu_);
+    request_count_ = 0;
+  }
+
+  std::set<grpc::string> clients() {
+    grpc::internal::MutexLock lock(&clients_mu_);
+    return clients_;
+  }
+
+ private:
+  void AddClient(const grpc::string& client) {
+    grpc::internal::MutexLock lock(&clients_mu_);
+    clients_.insert(client);
+  }
+
+  grpc::internal::Mutex mu_;
+  int request_count_;
+  grpc::internal::Mutex clients_mu_;
+  std::set<grpc::string> clients_;
+};
+
+class ServiceConfigEnd2endTest : public ::testing::Test {
+ protected:
+  ServiceConfigEnd2endTest()
+      : server_host_("localhost"),
+        kRequestMessage_("Live long and prosper."),
+        creds_(new SecureChannelCredentials(
+            grpc_fake_transport_security_credentials_create())) {
+    // Make the backup poller poll very frequently in order to pick up
+    // updates from all the subchannels's FDs.
+    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
+  }
+
+  void SetUp() override {
+    grpc_init();
+    response_generator_ =
+        grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
+  }
+
+  void TearDown() override {
+    for (size_t i = 0; i < servers_.size(); ++i) {
+      servers_[i]->Shutdown();
+    }
+    // Explicitly destroy all the members so that we can make sure grpc_shutdown
+    // has finished by the end of this function, and thus all the registered
+    // LB policy factories are removed.
+    stub_.reset();
+    servers_.clear();
+    creds_.reset();
+    grpc_shutdown_blocking();
+  }
+
+  void CreateServers(size_t num_servers,
+                     std::vector<int> ports = std::vector<int>()) {
+    servers_.clear();
+    for (size_t i = 0; i < num_servers; ++i) {
+      int port = 0;
+      if (ports.size() == num_servers) port = ports[i];
+      servers_.emplace_back(new ServerData(port));
+    }
+  }
+
+  void StartServer(size_t index) { servers_[index]->Start(server_host_); }
+
+  void StartServers(size_t num_servers,
+                    std::vector<int> ports = std::vector<int>()) {
+    CreateServers(num_servers, std::move(ports));
+    for (size_t i = 0; i < num_servers; ++i) {
+      StartServer(i);
+    }
+  }
+
+  grpc_core::Resolver::Result BuildFakeResults(const std::vector<int>& ports) {
+    grpc_core::Resolver::Result result;
+    for (const int& port : ports) {
+      char* lb_uri_str;
+      gpr_asprintf(&lb_uri_str, "ipv4:127.0.0.1:%d", port);
+      grpc_uri* lb_uri = grpc_uri_parse(lb_uri_str, true);
+      GPR_ASSERT(lb_uri != nullptr);
+      grpc_resolved_address address;
+      GPR_ASSERT(grpc_parse_uri(lb_uri, &address));
+      result.addresses.emplace_back(address.addr, address.len,
+                                    nullptr /* args */);
+      grpc_uri_destroy(lb_uri);
+      gpr_free(lb_uri_str);
+    }
+    return result;
+  }
+
+  void SetNextResolutionNoServiceConfig(const std::vector<int>& ports) {
+    grpc_core::ExecCtx exec_ctx;
+    grpc_core::Resolver::Result result = BuildFakeResults(ports);
+    response_generator_->SetResponse(result);
+  }
+
+  void SetNextResolutionValidServiceConfig(const std::vector<int>& ports) {
+    grpc_core::ExecCtx exec_ctx;
+    grpc_core::Resolver::Result result = BuildFakeResults(ports);
+    result.service_config =
+        grpc_core::ServiceConfig::Create("{}", &result.service_config_error);
+    response_generator_->SetResponse(result);
+  }
+
+  void SetNextResolutionInvalidServiceConfig(const std::vector<int>& ports) {
+    grpc_core::ExecCtx exec_ctx;
+    grpc_core::Resolver::Result result = BuildFakeResults(ports);
+    result.service_config =
+        grpc_core::ServiceConfig::Create("{", &result.service_config_error);
+    response_generator_->SetResponse(result);
+  }
+
+  void SetNextResolutionWithServiceConfig(const std::vector<int>& ports,
+                                          const char* svc_cfg) {
+    grpc_core::ExecCtx exec_ctx;
+    grpc_core::Resolver::Result result = BuildFakeResults(ports);
+    result.service_config =
+        grpc_core::ServiceConfig::Create(svc_cfg, &result.service_config_error);
+    response_generator_->SetResponse(result);
+  }
+
+  std::vector<int> GetServersPorts(size_t start_index = 0) {
+    std::vector<int> ports;
+    for (size_t i = start_index; i < servers_.size(); ++i) {
+      ports.push_back(servers_[i]->port_);
+    }
+    return ports;
+  }
+
+  std::unique_ptr<grpc::testing::EchoTestService::Stub> BuildStub(
+      const std::shared_ptr<Channel>& channel) {
+    return grpc::testing::EchoTestService::NewStub(channel);
+  }
+
+  std::shared_ptr<Channel> BuildChannel() {
+    ChannelArguments args;
+    args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
+                    response_generator_.get());
+    return ::grpc::CreateCustomChannel("fake:///", creds_, args);
+  }
+
+  std::shared_ptr<Channel> BuildChannelWithDefaultServiceConfig() {
+    ChannelArguments args;
+    args.SetServiceConfigJSON(ValidDefaultServiceConfig());
+    args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
+                    response_generator_.get());
+    return ::grpc::CreateCustomChannel("fake:///", creds_, args);
+  }
+
+  bool SendRpc(
+      const std::unique_ptr<grpc::testing::EchoTestService::Stub>& stub,
+      EchoResponse* response = nullptr, int timeout_ms = 1000,
+      Status* result = nullptr, bool wait_for_ready = false) {
+    const bool local_response = (response == nullptr);
+    if (local_response) response = new EchoResponse;
+    EchoRequest request;
+    request.set_message(kRequestMessage_);
+    ClientContext context;
+    context.set_deadline(grpc_timeout_milliseconds_to_deadline(timeout_ms));
+    if (wait_for_ready) context.set_wait_for_ready(true);
+    Status status = stub->Echo(&context, request, response);
+    if (result != nullptr) *result = status;
+    if (local_response) delete response;
+    return status.ok();
+  }
+
+  void CheckRpcSendOk(
+      const std::unique_ptr<grpc::testing::EchoTestService::Stub>& stub,
+      const grpc_core::DebugLocation& location, bool wait_for_ready = false) {
+    EchoResponse response;
+    Status status;
+    const bool success =
+        SendRpc(stub, &response, 2000, &status, wait_for_ready);
+    ASSERT_TRUE(success) << "From " << location.file() << ":" << location.line()
+                         << "\n"
+                         << "Error: " << status.error_message() << " "
+                         << status.error_details();
+    ASSERT_EQ(response.message(), kRequestMessage_)
+        << "From " << location.file() << ":" << location.line();
+    if (!success) abort();
+  }
+
+  void CheckRpcSendFailure(
+      const std::unique_ptr<grpc::testing::EchoTestService::Stub>& stub) {
+    const bool success = SendRpc(stub);
+    EXPECT_FALSE(success);
+  }
+
+  struct ServerData {
+    int port_;
+    std::unique_ptr<Server> server_;
+    MyTestServiceImpl service_;
+    std::unique_ptr<std::thread> thread_;
+    bool server_ready_ = false;
+    bool started_ = false;
+
+    explicit ServerData(int port = 0) {
+      port_ = port > 0 ? port : grpc_pick_unused_port_or_die();
+    }
+
+    void Start(const grpc::string& server_host) {
+      gpr_log(GPR_INFO, "starting server on port %d", port_);
+      started_ = true;
+      grpc::internal::Mutex mu;
+      grpc::internal::MutexLock lock(&mu);
+      grpc::internal::CondVar cond;
+      thread_.reset(new std::thread(
+          std::bind(&ServerData::Serve, this, server_host, &mu, &cond)));
+      cond.WaitUntil(&mu, [this] { return server_ready_; });
+      server_ready_ = false;
+      gpr_log(GPR_INFO, "server startup complete");
+    }
+
+    void Serve(const grpc::string& server_host, grpc::internal::Mutex* mu,
+               grpc::internal::CondVar* cond) {
+      std::ostringstream server_address;
+      server_address << server_host << ":" << port_;
+      ServerBuilder builder;
+      std::shared_ptr<ServerCredentials> creds(new SecureServerCredentials(
+          grpc_fake_transport_security_server_credentials_create()));
+      builder.AddListeningPort(server_address.str(), std::move(creds));
+      builder.RegisterService(&service_);
+      server_ = builder.BuildAndStart();
+      grpc::internal::MutexLock lock(mu);
+      server_ready_ = true;
+      cond->Signal();
+    }
+
+    void Shutdown() {
+      if (!started_) return;
+      server_->Shutdown(grpc_timeout_milliseconds_to_deadline(0));
+      thread_->join();
+      started_ = false;
+    }
+
+    void SetServingStatus(const grpc::string& service, bool serving) {
+      server_->GetHealthCheckService()->SetServingStatus(service, serving);
+    }
+  };
+
+  void ResetCounters() {
+    for (const auto& server : servers_) server->service_.ResetCounters();
+  }
+
+  void WaitForServer(
+      const std::unique_ptr<grpc::testing::EchoTestService::Stub>& stub,
+      size_t server_idx, const grpc_core::DebugLocation& location,
+      bool ignore_failure = false) {
+    do {
+      if (ignore_failure) {
+        SendRpc(stub);
+      } else {
+        CheckRpcSendOk(stub, location, true);
+      }
+    } while (servers_[server_idx]->service_.request_count() == 0);
+    ResetCounters();
+  }
+
+  bool WaitForChannelNotReady(Channel* channel, int timeout_seconds = 5) {
+    const gpr_timespec deadline =
+        grpc_timeout_seconds_to_deadline(timeout_seconds);
+    grpc_connectivity_state state;
+    while ((state = channel->GetState(false /* try_to_connect */)) ==
+           GRPC_CHANNEL_READY) {
+      if (!channel->WaitForStateChange(state, deadline)) return false;
+    }
+    return true;
+  }
+
+  bool WaitForChannelReady(Channel* channel, int timeout_seconds = 5) {
+    const gpr_timespec deadline =
+        grpc_timeout_seconds_to_deadline(timeout_seconds);
+    grpc_connectivity_state state;
+    while ((state = channel->GetState(true /* try_to_connect */)) !=
+           GRPC_CHANNEL_READY) {
+      if (!channel->WaitForStateChange(state, deadline)) return false;
+    }
+    return true;
+  }
+
+  bool SeenAllServers() {
+    for (const auto& server : servers_) {
+      if (server->service_.request_count() == 0) return false;
+    }
+    return true;
+  }
+
+  // Updates \a connection_order by appending to it the index of the newly
+  // connected server. Must be called after every single RPC.
+  void UpdateConnectionOrder(
+      const std::vector<std::unique_ptr<ServerData>>& servers,
+      std::vector<int>* connection_order) {
+    for (size_t i = 0; i < servers.size(); ++i) {
+      if (servers[i]->service_.request_count() == 1) {
+        // Was the server index known? If not, update connection_order.
+        const auto it =
+            std::find(connection_order->begin(), connection_order->end(), i);
+        if (it == connection_order->end()) {
+          connection_order->push_back(i);
+          return;
+        }
+      }
+    }
+  }
+
+  const char* ValidServiceConfigV1() { return "{\"version\": \"1\"}"; }
+
+  const char* ValidServiceConfigV2() { return "{\"version\": \"2\"}"; }
+
+  const char* ValidDefaultServiceConfig() {
+    return "{\"version\": \"valid_default\"}";
+  }
+
+  const char* InvalidDefaultServiceConfig() {
+    return "{\"version\": \"invalid_default\"}";
+  }
+
+  const grpc::string server_host_;
+  std::unique_ptr<grpc::testing::EchoTestService::Stub> stub_;
+  std::vector<std::unique_ptr<ServerData>> servers_;
+  grpc_core::RefCountedPtr<grpc_core::FakeResolverResponseGenerator>
+      response_generator_;
+  const grpc::string kRequestMessage_;
+  std::shared_ptr<ChannelCredentials> creds_;
+};
+
+TEST_F(ServiceConfigEnd2endTest, NoServiceConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionNoServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ("", channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest, NoServiceConfigWithDefaultConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannelWithDefaultServiceConfig();
+  auto stub = BuildStub(channel);
+  SetNextResolutionNoServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidDefaultServiceConfig(),
+               channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest, InvalidServiceConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendFailure(stub);
+}
+
+TEST_F(ServiceConfigEnd2endTest, InvalidServiceConfigWithDefaultConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannelWithDefaultServiceConfig();
+  auto stub = BuildStub(channel);
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidDefaultServiceConfig(),
+               channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest, ValidServiceConfigUpdatesTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionWithServiceConfig(GetServersPorts(), ValidServiceConfigV1());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV1(), channel->GetServiceConfigJSON().c_str());
+  SetNextResolutionWithServiceConfig(GetServersPorts(), ValidServiceConfigV2());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV2(), channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest,
+       NoServiceConfigUpdateAfterValidServiceConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionWithServiceConfig(GetServersPorts(), ValidServiceConfigV1());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV1(), channel->GetServiceConfigJSON().c_str());
+  SetNextResolutionNoServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ("", channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest,
+       NoServiceConfigUpdateAfterValidServiceConfigWithDefaultConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannelWithDefaultServiceConfig();
+  auto stub = BuildStub(channel);
+  SetNextResolutionWithServiceConfig(GetServersPorts(), ValidServiceConfigV1());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV1(), channel->GetServiceConfigJSON().c_str());
+  SetNextResolutionNoServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidDefaultServiceConfig(),
+               channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest,
+       InvalidServiceConfigUpdateAfterValidServiceConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionWithServiceConfig(GetServersPorts(), ValidServiceConfigV1());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV1(), channel->GetServiceConfigJSON().c_str());
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV1(), channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest,
+       InvalidServiceConfigUpdateAfterValidServiceConfigWithDefaultConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannelWithDefaultServiceConfig();
+  auto stub = BuildStub(channel);
+  SetNextResolutionWithServiceConfig(GetServersPorts(), ValidServiceConfigV1());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV1(), channel->GetServiceConfigJSON().c_str());
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ(ValidServiceConfigV1(), channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest,
+       ValidServiceConfigAfterInvalidServiceConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendFailure(stub);
+  SetNextResolutionValidServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+}
+
+TEST_F(ServiceConfigEnd2endTest, NoServiceConfigAfterInvalidServiceConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendFailure(stub);
+  SetNextResolutionNoServiceConfig(GetServersPorts());
+  CheckRpcSendOk(stub, DEBUG_LOCATION);
+  EXPECT_STREQ("", channel->GetServiceConfigJSON().c_str());
+}
+
+TEST_F(ServiceConfigEnd2endTest,
+       AnotherInvalidServiceConfigAfterInvalidServiceConfigTest) {
+  StartServers(1);
+  auto channel = BuildChannel();
+  auto stub = BuildStub(channel);
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendFailure(stub);
+  SetNextResolutionInvalidServiceConfig(GetServersPorts());
+  CheckRpcSendFailure(stub);
+}
+
+}  // namespace
+}  // namespace testing
+}  // namespace grpc
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  grpc::testing::TestEnvironment env(argc, argv);
+  const auto result = RUN_ALL_TESTS();
+  return result;
+}
diff --git a/test/cpp/end2end/shutdown_test.cc b/test/cpp/end2end/shutdown_test.cc
index da42178..9e92536 100644
--- a/test/cpp/end2end/shutdown_test.cc
+++ b/test/cpp/end2end/shutdown_test.cc
@@ -86,7 +86,7 @@
     ChannelArguments args;
     auto channel_creds =
         GetCredentialsProvider()->GetChannelCredentials(GetParam(), &args);
-    channel_ = CreateCustomChannel(target, channel_creds, args);
+    channel_ = ::grpc::CreateCustomChannel(target, channel_creds, args);
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
diff --git a/test/cpp/end2end/streaming_throughput_test.cc b/test/cpp/end2end/streaming_throughput_test.cc
index 4406565..0c10957 100644
--- a/test/cpp/end2end/streaming_throughput_test.cc
+++ b/test/cpp/end2end/streaming_throughput_test.cc
@@ -145,8 +145,8 @@
   void TearDown() override { server_->Shutdown(); }
 
   void ResetStub() {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
 
diff --git a/test/cpp/end2end/test_service_impl.cc b/test/cpp/end2end/test_service_impl.cc
index abbb669..4078cdf 100644
--- a/test/cpp/end2end/test_service_impl.cc
+++ b/test/cpp/end2end/test_service_impl.cc
@@ -143,7 +143,6 @@
 
 Status TestServiceImpl::Echo(ServerContext* context, const EchoRequest* request,
                              EchoResponse* response) {
-  gpr_log(GPR_DEBUG, "Request message was %s", request->message().c_str());
   // A bit of sleep to make sure that short deadline tests fail
   if (request->has_param() && request->param().server_sleep_us() > 0) {
     gpr_sleep_until(
@@ -201,6 +200,17 @@
     EXPECT_FALSE(context->IsCancelled());
   }
 
+  if (request->has_param() && request->param().echo_metadata_initially()) {
+    const std::multimap<grpc::string_ref, grpc::string_ref>& client_metadata =
+        context->client_metadata();
+    for (std::multimap<grpc::string_ref, grpc::string_ref>::const_iterator
+             iter = client_metadata.begin();
+         iter != client_metadata.end(); ++iter) {
+      context->AddInitialMetadata(ToString(iter->first),
+                                  ToString(iter->second));
+    }
+  }
+
   if (request->has_param() && request->param().echo_metadata()) {
     const std::multimap<grpc::string_ref, grpc::string_ref>& client_metadata =
         context->client_metadata();
@@ -380,6 +390,18 @@
     EXPECT_FALSE(context->IsCancelled());
   }
 
+  if (request->has_param() && request->param().echo_metadata_initially()) {
+    const std::multimap<grpc::string_ref, grpc::string_ref>& client_metadata =
+        context->client_metadata();
+    for (std::multimap<grpc::string_ref, grpc::string_ref>::const_iterator
+             iter = client_metadata.begin();
+         iter != client_metadata.end(); ++iter) {
+      context->AddInitialMetadata(ToString(iter->first),
+                                  ToString(iter->second));
+    }
+    controller->SendInitialMetadata([](bool ok) { EXPECT_TRUE(ok); });
+  }
+
   if (request->has_param() && request->param().echo_metadata()) {
     const std::multimap<grpc::string_ref, grpc::string_ref>& client_metadata =
         context->client_metadata();
@@ -590,8 +612,9 @@
    public:
     Reactor() {}
     void OnStarted(ServerContext* context, EchoResponse* response) override {
-      ctx_ = context;
-      response_ = response;
+      // Assign ctx_ and response_ as late as possible to increase likelihood of
+      // catching any races
+
       // If 'server_try_cancel' is set in the metadata, the RPC is cancelled by
       // the server by calling ServerContext::TryCancel() depending on the
       // value:
@@ -603,22 +626,26 @@
       server_try_cancel_ = GetIntValueFromMetadata(
           kServerTryCancelRequest, context->client_metadata(), DO_NOT_CANCEL);
 
-      response_->set_message("");
+      response->set_message("");
 
       if (server_try_cancel_ == CANCEL_BEFORE_PROCESSING) {
-        ServerTryCancelNonblocking(ctx_);
-        return;
+        ServerTryCancelNonblocking(context);
+        ctx_ = context;
+      } else {
+        if (server_try_cancel_ == CANCEL_DURING_PROCESSING) {
+          context->TryCancel();
+          // Don't wait for it here
+        }
+        ctx_ = context;
+        response_ = response;
+        StartRead(&request_);
       }
 
-      if (server_try_cancel_ == CANCEL_DURING_PROCESSING) {
-        ctx_->TryCancel();
-        // Don't wait for it here
-      }
-
-      StartRead(&request_);
+      on_started_done_ = true;
     }
     void OnDone() override { delete this; }
     void OnCancel() override {
+      EXPECT_TRUE(on_started_done_);
       EXPECT_TRUE(ctx_->IsCancelled());
       FinishOnce(Status::CANCELLED);
     }
@@ -658,6 +685,7 @@
     int server_try_cancel_;
     std::mutex finish_mu_;
     bool finished_{false};
+    bool on_started_done_{false};
   };
 
   return new Reactor;
@@ -674,8 +702,9 @@
     Reactor() {}
     void OnStarted(ServerContext* context,
                    const EchoRequest* request) override {
-      ctx_ = context;
-      request_ = request;
+      // Assign ctx_ and request_ as late as possible to increase likelihood of
+      // catching any races
+
       // If 'server_try_cancel' is set in the metadata, the RPC is cancelled by
       // the server by calling ServerContext::TryCancel() depending on the
       // value:
@@ -692,19 +721,23 @@
           kServerResponseStreamsToSend, context->client_metadata(),
           kServerDefaultResponseStreamsToSend);
       if (server_try_cancel_ == CANCEL_BEFORE_PROCESSING) {
-        ServerTryCancelNonblocking(ctx_);
-        return;
+        ServerTryCancelNonblocking(context);
+        ctx_ = context;
+      } else {
+        if (server_try_cancel_ == CANCEL_DURING_PROCESSING) {
+          context->TryCancel();
+        }
+        ctx_ = context;
+        request_ = request;
+        if (num_msgs_sent_ < server_responses_to_send_) {
+          NextWrite();
+        }
       }
-
-      if (server_try_cancel_ == CANCEL_DURING_PROCESSING) {
-        ctx_->TryCancel();
-      }
-      if (num_msgs_sent_ < server_responses_to_send_) {
-        NextWrite();
-      }
+      on_started_done_ = true;
     }
     void OnDone() override { delete this; }
     void OnCancel() override {
+      EXPECT_TRUE(on_started_done_);
       EXPECT_TRUE(ctx_->IsCancelled());
       FinishOnce(Status::CANCELLED);
     }
@@ -754,6 +787,7 @@
     int server_responses_to_send_;
     std::mutex finish_mu_;
     bool finished_{false};
+    bool on_started_done_{false};
   };
   return new Reactor;
 }
@@ -765,7 +799,9 @@
    public:
     Reactor() {}
     void OnStarted(ServerContext* context) override {
-      ctx_ = context;
+      // Assign ctx_ as late as possible to increase likelihood of catching any
+      // races
+
       // If 'server_try_cancel' is set in the metadata, the RPC is cancelled by
       // the server by calling ServerContext::TryCancel() depending on the
       // value:
@@ -779,18 +815,20 @@
       server_write_last_ = GetIntValueFromMetadata(
           kServerFinishAfterNReads, context->client_metadata(), 0);
       if (server_try_cancel_ == CANCEL_BEFORE_PROCESSING) {
-        ServerTryCancelNonblocking(ctx_);
-        return;
+        ServerTryCancelNonblocking(context);
+        ctx_ = context;
+      } else {
+        if (server_try_cancel_ == CANCEL_DURING_PROCESSING) {
+          context->TryCancel();
+        }
+        ctx_ = context;
+        StartRead(&request_);
       }
-
-      if (server_try_cancel_ == CANCEL_DURING_PROCESSING) {
-        ctx_->TryCancel();
-      }
-
-      StartRead(&request_);
+      on_started_done_ = true;
     }
     void OnDone() override { delete this; }
     void OnCancel() override {
+      EXPECT_TRUE(on_started_done_);
       EXPECT_TRUE(ctx_->IsCancelled());
       FinishOnce(Status::CANCELLED);
     }
@@ -840,6 +878,7 @@
     int server_write_last_;
     std::mutex finish_mu_;
     bool finished_{false};
+    bool on_started_done_{false};
   };
 
   return new Reactor;
diff --git a/test/cpp/end2end/thread_stress_test.cc b/test/cpp/end2end/thread_stress_test.cc
index e30ce0d..eb8e795 100644
--- a/test/cpp/end2end/thread_stress_test.cc
+++ b/test/cpp/end2end/thread_stress_test.cc
@@ -25,6 +25,7 @@
 #include <grpcpp/channel.h>
 #include <grpcpp/client_context.h>
 #include <grpcpp/create_channel.h>
+#include <grpcpp/impl/codegen/sync.h>
 #include <grpcpp/resource_quota.h>
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
@@ -95,8 +96,8 @@
 class CommonStressTestInsecure : public CommonStressTest<Service> {
  public:
   void ResetStub() override {
-    std::shared_ptr<Channel> channel =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    std::shared_ptr<Channel> channel = grpc::CreateChannel(
+        server_address_.str(), InsecureChannelCredentials());
     this->stub_ = grpc::testing::EchoTestService::NewStub(channel);
   }
   bool AllowExhaustion() override { return false; }
@@ -188,7 +189,7 @@
   }
   void TearDown() override {
     {
-      std::unique_lock<std::mutex> l(mu_);
+      grpc::internal::MutexLock l(&mu_);
       this->TearDownStart();
       shutting_down_ = true;
       cq_->Shutdown();
@@ -229,7 +230,7 @@
     }
   }
   void RefreshContext(int i) {
-    std::unique_lock<std::mutex> l(mu_);
+    grpc::internal::MutexLock l(&mu_);
     if (!shutting_down_) {
       contexts_[i].state = Context::READY;
       contexts_[i].srv_ctx.reset(new ServerContext);
@@ -253,7 +254,7 @@
   ::grpc::testing::EchoTestService::AsyncService service_;
   std::unique_ptr<ServerCompletionQueue> cq_;
   bool shutting_down_;
-  std::mutex mu_;
+  grpc::internal::Mutex mu_;
   std::vector<std::thread> server_threads_;
 };
 
@@ -341,9 +342,9 @@
   }
 
   void Wait() {
-    std::unique_lock<std::mutex> l(mu_);
+    grpc::internal::MutexLock l(&mu_);
     while (rpcs_outstanding_ != 0) {
-      cv_.wait(l);
+      cv_.Wait(&mu_);
     }
 
     cq_.Shutdown();
@@ -366,7 +367,7 @@
       call->response_reader->Finish(&call->response, &call->status,
                                     (void*)call);
 
-      std::unique_lock<std::mutex> l(mu_);
+      grpc::internal::MutexLock l(&mu_);
       rpcs_outstanding_++;
     }
   }
@@ -384,20 +385,20 @@
 
       bool notify;
       {
-        std::unique_lock<std::mutex> l(mu_);
+        grpc::internal::MutexLock l(&mu_);
         rpcs_outstanding_--;
         notify = (rpcs_outstanding_ == 0);
       }
       if (notify) {
-        cv_.notify_all();
+        cv_.Signal();
       }
     }
   }
 
   Common common_;
   CompletionQueue cq_;
-  std::mutex mu_;
-  std::condition_variable cv_;
+  grpc::internal::Mutex mu_;
+  grpc::internal::CondVar cv_;
   int rpcs_outstanding_;
 };
 
diff --git a/test/cpp/end2end/time_change_test.cc b/test/cpp/end2end/time_change_test.cc
index 7f4e3ca..688549e 100644
--- a/test/cpp/end2end/time_change_test.cc
+++ b/test/cpp/end2end/time_change_test.cc
@@ -139,7 +139,7 @@
         "--address=" + addr,
     }));
     GPR_ASSERT(server_);
-    channel_ = CreateChannel(addr, InsecureChannelCredentials());
+    channel_ = grpc::CreateChannel(addr, InsecureChannelCredentials());
     GPR_ASSERT(channel_);
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
diff --git a/test/cpp/end2end/xds_end2end_test.cc b/test/cpp/end2end/xds_end2end_test.cc
index 61e759c..b876e06 100644
--- a/test/cpp/end2end/xds_end2end_test.cc
+++ b/test/cpp/end2end/xds_end2end_test.cc
@@ -33,6 +33,7 @@
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
 
+#include "src/core/ext/filters/client_channel/backup_poller.h"
 #include "src/core/ext/filters/client_channel/parse_address.h"
 #include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
@@ -84,32 +85,32 @@
 class CountedService : public ServiceType {
  public:
   size_t request_count() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     return request_count_;
   }
 
   size_t response_count() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     return response_count_;
   }
 
   void IncreaseResponseCount() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     ++response_count_;
   }
   void IncreaseRequestCount() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     ++request_count_;
   }
 
   void ResetCounters() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     request_count_ = 0;
     response_count_ = 0;
   }
 
  protected:
-  std::mutex mu_;
+  grpc::internal::Mutex mu_;
 
  private:
   size_t request_count_ = 0;
@@ -145,18 +146,18 @@
   void Shutdown() {}
 
   std::set<grpc::string> clients() {
-    std::unique_lock<std::mutex> lock(clients_mu_);
+    grpc::internal::MutexLock lock(&clients_mu_);
     return clients_;
   }
 
  private:
   void AddClient(const grpc::string& client) {
-    std::unique_lock<std::mutex> lock(clients_mu_);
+    grpc::internal::MutexLock lock(&clients_mu_);
     clients_.insert(client);
   }
 
-  std::mutex mu_;
-  std::mutex clients_mu_;
+  grpc::internal::Mutex mu_;
+  grpc::internal::Mutex clients_mu_;
   std::set<grpc::string> clients_;
 };
 
@@ -208,6 +209,10 @@
     // TODO(juanlishen): Clean up the scoping.
     gpr_log(GPR_INFO, "LB[%p]: BalanceLoad", this);
     {
+      grpc::internal::MutexLock lock(&mu_);
+      if (serverlist_done_) goto done;
+    }
+    {
       // Balancer shouldn't receive the call credentials metadata.
       EXPECT_EQ(context->client_metadata().find(g_kCallCredsMdKey),
                 context->client_metadata().end());
@@ -230,7 +235,7 @@
       }
 
       {
-        std::unique_lock<std::mutex> lock(mu_);
+        grpc::internal::MutexLock lock(&mu_);
         responses_and_delays = responses_and_delays_;
       }
       for (const auto& response_and_delay : responses_and_delays) {
@@ -238,8 +243,8 @@
                      response_and_delay.second);
       }
       {
-        std::unique_lock<std::mutex> lock(mu_);
-        serverlist_cond_.wait(lock, [this] { return serverlist_done_; });
+        grpc::internal::MutexLock lock(&mu_);
+        serverlist_cond_.WaitUntil(&mu_, [this] { return serverlist_done_; });
       }
 
       if (client_load_reporting_interval_seconds_ > 0) {
@@ -250,7 +255,7 @@
           GPR_ASSERT(request.has_client_stats());
           // We need to acquire the lock here in order to prevent the notify_one
           // below from firing before its corresponding wait is executed.
-          std::lock_guard<std::mutex> lock(mu_);
+          grpc::internal::MutexLock lock(&mu_);
           client_stats_.num_calls_started +=
               request.client_stats().num_calls_started();
           client_stats_.num_calls_finished +=
@@ -267,7 +272,7 @@
                 drop_token_count.num_calls();
           }
           load_report_ready_ = true;
-          load_report_cond_.notify_one();
+          load_report_cond_.Signal();
         }
       }
     }
@@ -277,12 +282,12 @@
   }
 
   void add_response(const LoadBalanceResponse& response, int send_after_ms) {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     responses_and_delays_.push_back(std::make_pair(response, send_after_ms));
   }
 
   void Shutdown() {
-    std::unique_lock<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     NotifyDoneWithServerlistsLocked();
     responses_and_delays_.clear();
     client_stats_.Reset();
@@ -314,21 +319,21 @@
   }
 
   const ClientStats& WaitForLoadReport() {
-    std::unique_lock<std::mutex> lock(mu_);
-    load_report_cond_.wait(lock, [this] { return load_report_ready_; });
+    grpc::internal::MutexLock lock(&mu_);
+    load_report_cond_.WaitUntil(&mu_, [this] { return load_report_ready_; });
     load_report_ready_ = false;
     return client_stats_;
   }
 
   void NotifyDoneWithServerlists() {
-    std::lock_guard<std::mutex> lock(mu_);
+    grpc::internal::MutexLock lock(&mu_);
     NotifyDoneWithServerlistsLocked();
   }
 
   void NotifyDoneWithServerlistsLocked() {
     if (!serverlist_done_) {
       serverlist_done_ = true;
-      serverlist_cond_.notify_all();
+      serverlist_cond_.Broadcast();
     }
   }
 
@@ -347,10 +352,10 @@
 
   const int client_load_reporting_interval_seconds_;
   std::vector<ResponseDelayPair> responses_and_delays_;
-  std::mutex mu_;
-  std::condition_variable load_report_cond_;
+  grpc::internal::Mutex mu_;
+  grpc::internal::CondVar load_report_cond_;
   bool load_report_ready_ = false;
-  std::condition_variable serverlist_cond_;
+  grpc::internal::CondVar serverlist_cond_;
   bool serverlist_done_ = false;
   ClientStats client_stats_;
 };
@@ -366,7 +371,7 @@
             client_load_reporting_interval_seconds) {
     // Make the backup poller poll very frequently in order to pick up
     // updates from all the subchannels's FDs.
-    gpr_setenv("GRPC_CLIENT_CHANNEL_BACKUP_POLL_INTERVAL_MS", "1");
+    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
   }
 
   void SetUp() override {
@@ -409,7 +414,9 @@
                  const grpc::string& expected_targets = "") {
     ChannelArguments args;
     // TODO(juanlishen): Add setter to ChannelArguments.
-    args.SetInt(GRPC_ARG_XDS_FALLBACK_TIMEOUT_MS, fallback_timeout);
+    if (fallback_timeout > 0) {
+      args.SetInt(GRPC_ARG_XDS_FALLBACK_TIMEOUT_MS, fallback_timeout);
+    }
     args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
                     response_generator_.get());
     if (!expected_targets.empty()) {
@@ -428,7 +435,7 @@
             channel_creds, call_creds, nullptr)));
     call_creds->Unref();
     channel_creds->Unref();
-    channel_ = CreateCustomChannel(uri.str(), creds, args);
+    channel_ = ::grpc::CreateCustomChannel(uri.str(), creds, args);
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
@@ -524,8 +531,10 @@
     grpc_core::Resolver::Result result;
     result.addresses = CreateLbAddressesFromPortList(ports);
     if (service_config_json != nullptr) {
+      grpc_error* error = GRPC_ERROR_NONE;
       result.service_config =
-          grpc_core::ServiceConfig::Create(service_config_json);
+          grpc_core::ServiceConfig::Create(service_config_json, &error);
+      GRPC_ERROR_UNREF(error);
     }
     grpc_arg arg = grpc_core::FakeResolverResponseGenerator::MakeChannelArg(
         lb_channel_response_generator == nullptr
@@ -555,8 +564,10 @@
     grpc_core::Resolver::Result result;
     result.addresses = CreateLbAddressesFromPortList(ports);
     if (service_config_json != nullptr) {
+      grpc_error* error = GRPC_ERROR_NONE;
       result.service_config =
-          grpc_core::ServiceConfig::Create(service_config_json);
+          grpc_core::ServiceConfig::Create(service_config_json, &error);
+      GRPC_ERROR_UNREF(error);
     }
     if (lb_channel_response_generator == nullptr) {
       lb_channel_response_generator = lb_channel_response_generator_.get();
@@ -629,22 +640,22 @@
       gpr_log(GPR_INFO, "starting %s server on port %d", type_.c_str(), port_);
       GPR_ASSERT(!running_);
       running_ = true;
-      std::mutex mu;
+      grpc::internal::Mutex mu;
       // We need to acquire the lock here in order to prevent the notify_one
       // by ServerThread::Serve from firing before the wait below is hit.
-      std::unique_lock<std::mutex> lock(mu);
-      std::condition_variable cond;
+      grpc::internal::MutexLock lock(&mu);
+      grpc::internal::CondVar cond;
       thread_.reset(new std::thread(
           std::bind(&ServerThread::Serve, this, server_host, &mu, &cond)));
-      cond.wait(lock);
+      cond.Wait(&mu);
       gpr_log(GPR_INFO, "%s server startup complete", type_.c_str());
     }
 
-    void Serve(const grpc::string& server_host, std::mutex* mu,
-               std::condition_variable* cond) {
+    void Serve(const grpc::string& server_host, grpc::internal::Mutex* mu,
+               grpc::internal::CondVar* cond) {
       // We need to acquire the lock here in order to prevent the notify_one
       // below from firing before its corresponding wait is executed.
-      std::lock_guard<std::mutex> lock(*mu);
+      grpc::internal::MutexLock lock(mu);
       std::ostringstream server_address;
       server_address << server_host << ":" << port_;
       ServerBuilder builder;
@@ -653,7 +664,7 @@
       builder.AddListeningPort(server_address.str(), creds);
       builder.RegisterService(&service_);
       server_ = builder.BuildAndStart();
-      cond->notify_one();
+      cond->Signal();
     }
 
     void Shutdown() {
@@ -847,38 +858,228 @@
   EXPECT_EQ(1U, balancers_[0]->service_.response_count());
 }
 
-// The fallback tests are deferred because the fallback mode hasn't been
-// supported yet.
-
-// TODO(juanlishen): Add TEST_F(SingleBalancerTest, Fallback)
-
-// TODO(juanlishen): Add TEST_F(SingleBalancerTest, FallbackUpdate)
-
-// TODO(juanlishen): Add TEST_F(SingleBalancerTest,
-// FallbackEarlyWhenBalancerChannelFails)
-
-TEST_F(SingleBalancerTest, BackendsRestart) {
-  SetNextResolution({}, kDefaultServiceConfig_.c_str());
+TEST_F(SingleBalancerTest, Fallback) {
+  const int kFallbackTimeoutMs = 200 * grpc_test_slowdown_factor();
+  const int kServerlistDelayMs = 500 * grpc_test_slowdown_factor();
+  const size_t kNumBackendsInResolution = backends_.size() / 2;
+  ResetStub(kFallbackTimeoutMs);
+  SetNextResolution(GetBackendPorts(0, kNumBackendsInResolution),
+                    kDefaultServiceConfig_.c_str());
   SetNextResolutionForLbChannelAllBalancers();
-  const size_t kNumRpcsPerAddress = 100;
+  // Send non-empty serverlist only after kServerlistDelayMs.
   ScheduleResponseForBalancer(
-      0, BalancerServiceImpl::BuildResponseForBackends(GetBackendPorts(), {}),
-      0);
-  // Make sure that trying to connect works without a call.
-  channel_->GetState(true /* try_to_connect */);
-  // Send kNumRpcsPerAddress RPCs per server.
-  CheckRpcSendOk(kNumRpcsPerAddress * num_backends_);
-  balancers_[0]->service_.NotifyDoneWithServerlists();
+      0,
+      BalancerServiceImpl::BuildResponseForBackends(
+          GetBackendPorts(kNumBackendsInResolution /* start_index */), {}),
+      kServerlistDelayMs);
+  // Wait until all the fallback backends are reachable.
+  WaitForAllBackends(1 /* num_requests_multiple_of */, 0 /* start_index */,
+                     kNumBackendsInResolution /* stop_index */);
+  gpr_log(GPR_INFO, "========= BEFORE FIRST BATCH ==========");
+  CheckRpcSendOk(kNumBackendsInResolution);
+  gpr_log(GPR_INFO, "========= DONE WITH FIRST BATCH ==========");
+  // Fallback is used: each backend returned by the resolver should have
+  // gotten one request.
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
+    EXPECT_EQ(1U, backends_[i]->service_.request_count());
+  }
+  for (size_t i = kNumBackendsInResolution; i < backends_.size(); ++i) {
+    EXPECT_EQ(0U, backends_[i]->service_.request_count());
+  }
+  // Wait until the serverlist reception has been processed and all backends
+  // in the serverlist are reachable.
+  WaitForAllBackends(1 /* num_requests_multiple_of */,
+                     kNumBackendsInResolution /* start_index */);
+  gpr_log(GPR_INFO, "========= BEFORE SECOND BATCH ==========");
+  CheckRpcSendOk(backends_.size() - kNumBackendsInResolution);
+  gpr_log(GPR_INFO, "========= DONE WITH SECOND BATCH ==========");
+  // Serverlist is used: each backend returned by the balancer should
+  // have gotten one request.
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
+    EXPECT_EQ(0U, backends_[i]->service_.request_count());
+  }
+  for (size_t i = kNumBackendsInResolution; i < backends_.size(); ++i) {
+    EXPECT_EQ(1U, backends_[i]->service_.request_count());
+  }
   // The balancer got a single request.
   EXPECT_EQ(1U, balancers_[0]->service_.request_count());
   // and sent a single response.
   EXPECT_EQ(1U, balancers_[0]->service_.response_count());
+}
+
+TEST_F(SingleBalancerTest, FallbackUpdate) {
+  const int kFallbackTimeoutMs = 200 * grpc_test_slowdown_factor();
+  const int kServerlistDelayMs = 500 * grpc_test_slowdown_factor();
+  const size_t kNumBackendsInResolution = backends_.size() / 3;
+  const size_t kNumBackendsInResolutionUpdate = backends_.size() / 3;
+  ResetStub(kFallbackTimeoutMs);
+  SetNextResolution(GetBackendPorts(0, kNumBackendsInResolution),
+                    kDefaultServiceConfig_.c_str());
+  SetNextResolutionForLbChannelAllBalancers();
+  // Send non-empty serverlist only after kServerlistDelayMs.
+  ScheduleResponseForBalancer(
+      0,
+      BalancerServiceImpl::BuildResponseForBackends(
+          GetBackendPorts(kNumBackendsInResolution +
+                          kNumBackendsInResolutionUpdate /* start_index */),
+          {}),
+      kServerlistDelayMs);
+  // Wait until all the fallback backends are reachable.
+  WaitForAllBackends(1 /* num_requests_multiple_of */, 0 /* start_index */,
+                     kNumBackendsInResolution /* stop_index */);
+  gpr_log(GPR_INFO, "========= BEFORE FIRST BATCH ==========");
+  CheckRpcSendOk(kNumBackendsInResolution);
+  gpr_log(GPR_INFO, "========= DONE WITH FIRST BATCH ==========");
+  // Fallback is used: each backend returned by the resolver should have
+  // gotten one request.
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
+    EXPECT_EQ(1U, backends_[i]->service_.request_count());
+  }
+  for (size_t i = kNumBackendsInResolution; i < backends_.size(); ++i) {
+    EXPECT_EQ(0U, backends_[i]->service_.request_count());
+  }
+  SetNextResolution(GetBackendPorts(kNumBackendsInResolution,
+                                    kNumBackendsInResolution +
+                                        kNumBackendsInResolutionUpdate),
+                    kDefaultServiceConfig_.c_str());
+  // Wait until the resolution update has been processed and all the new
+  // fallback backends are reachable.
+  WaitForAllBackends(1 /* num_requests_multiple_of */,
+                     kNumBackendsInResolution /* start_index */,
+                     kNumBackendsInResolution +
+                         kNumBackendsInResolutionUpdate /* stop_index */);
+  gpr_log(GPR_INFO, "========= BEFORE SECOND BATCH ==========");
+  CheckRpcSendOk(kNumBackendsInResolutionUpdate);
+  gpr_log(GPR_INFO, "========= DONE WITH SECOND BATCH ==========");
+  // The resolution update is used: each backend in the resolution update should
+  // have gotten one request.
+  for (size_t i = 0; i < kNumBackendsInResolution; ++i) {
+    EXPECT_EQ(0U, backends_[i]->service_.request_count());
+  }
+  for (size_t i = kNumBackendsInResolution;
+       i < kNumBackendsInResolution + kNumBackendsInResolutionUpdate; ++i) {
+    EXPECT_EQ(1U, backends_[i]->service_.request_count());
+  }
+  for (size_t i = kNumBackendsInResolution + kNumBackendsInResolutionUpdate;
+       i < backends_.size(); ++i) {
+    EXPECT_EQ(0U, backends_[i]->service_.request_count());
+  }
+  // Wait until the serverlist reception has been processed and all backends
+  // in the serverlist are reachable.
+  WaitForAllBackends(1 /* num_requests_multiple_of */,
+                     kNumBackendsInResolution +
+                         kNumBackendsInResolutionUpdate /* start_index */);
+  gpr_log(GPR_INFO, "========= BEFORE THIRD BATCH ==========");
+  CheckRpcSendOk(backends_.size() - kNumBackendsInResolution -
+                 kNumBackendsInResolutionUpdate);
+  gpr_log(GPR_INFO, "========= DONE WITH THIRD BATCH ==========");
+  // Serverlist is used: each backend returned by the balancer should
+  // have gotten one request.
+  for (size_t i = 0;
+       i < kNumBackendsInResolution + kNumBackendsInResolutionUpdate; ++i) {
+    EXPECT_EQ(0U, backends_[i]->service_.request_count());
+  }
+  for (size_t i = kNumBackendsInResolution + kNumBackendsInResolutionUpdate;
+       i < backends_.size(); ++i) {
+    EXPECT_EQ(1U, backends_[i]->service_.request_count());
+  }
+  // The balancer got a single request.
+  EXPECT_EQ(1U, balancers_[0]->service_.request_count());
+  // and sent a single response.
+  EXPECT_EQ(1U, balancers_[0]->service_.response_count());
+}
+
+TEST_F(SingleBalancerTest, FallbackEarlyWhenBalancerChannelFails) {
+  const int kFallbackTimeoutMs = 10000 * grpc_test_slowdown_factor();
+  ResetStub(kFallbackTimeoutMs);
+  // Return an unreachable balancer and one fallback backend.
+  SetNextResolution({backends_[0]->port_}, kDefaultServiceConfig_.c_str());
+  SetNextResolutionForLbChannel({grpc_pick_unused_port_or_die()});
+  // Send RPC with deadline less than the fallback timeout and make sure it
+  // succeeds.
+  CheckRpcSendOk(/* times */ 1, /* timeout_ms */ 1000,
+                 /* wait_for_ready */ false);
+}
+
+TEST_F(SingleBalancerTest, FallbackEarlyWhenBalancerCallFails) {
+  const int kFallbackTimeoutMs = 10000 * grpc_test_slowdown_factor();
+  ResetStub(kFallbackTimeoutMs);
+  // Return one balancer and one fallback backend.
+  SetNextResolution({backends_[0]->port_}, kDefaultServiceConfig_.c_str());
+  SetNextResolutionForLbChannelAllBalancers();
+  // Balancer drops call without sending a serverlist.
+  balancers_[0]->service_.NotifyDoneWithServerlists();
+  // Send RPC with deadline less than the fallback timeout and make sure it
+  // succeeds.
+  CheckRpcSendOk(/* times */ 1, /* timeout_ms */ 1000,
+                 /* wait_for_ready */ false);
+}
+
+TEST_F(SingleBalancerTest, FallbackModeIsExitedWhenBalancerSaysToDropAllCalls) {
+  // Return an unreachable balancer and one fallback backend.
+  SetNextResolution({backends_[0]->port_}, kDefaultServiceConfig_.c_str());
+  SetNextResolutionForLbChannel({grpc_pick_unused_port_or_die()});
+  // Enter fallback mode because the LB channel fails to connect.
+  WaitForBackend(0);
+  // Return a new balancer that sends an empty serverlist.
+  ScheduleResponseForBalancer(
+      0, BalancerServiceImpl::BuildResponseForBackends({}, {}), 0);
+  SetNextResolutionForLbChannelAllBalancers();
+  // Send RPCs until failure.
+  gpr_timespec deadline = gpr_time_add(
+      gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_millis(5000, GPR_TIMESPAN));
+  do {
+    auto status = SendRpc();
+    if (!status.ok()) break;
+  } while (gpr_time_cmp(gpr_now(GPR_CLOCK_REALTIME), deadline) < 0);
+  CheckRpcSendFailure();
+}
+
+TEST_F(SingleBalancerTest, FallbackModeIsExitedAfterChildRready) {
+  // Return an unreachable balancer and one fallback backend.
+  SetNextResolution({backends_[0]->port_}, kDefaultServiceConfig_.c_str());
+  SetNextResolutionForLbChannel({grpc_pick_unused_port_or_die()});
+  // Enter fallback mode because the LB channel fails to connect.
+  WaitForBackend(0);
+  // Return a new balancer that sends a dead backend.
+  ShutdownBackend(1);
+  ScheduleResponseForBalancer(
+      0,
+      BalancerServiceImpl::BuildResponseForBackends({backends_[1]->port_}, {}),
+      0);
+  SetNextResolutionForLbChannelAllBalancers();
+  // The state (TRANSIENT_FAILURE) update from the child policy will be ignored
+  // because we are still in fallback mode.
+  gpr_timespec deadline = gpr_time_add(
+      gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_millis(5000, GPR_TIMESPAN));
+  // Send 5 seconds worth of RPCs.
+  do {
+    CheckRpcSendOk();
+  } while (gpr_time_cmp(gpr_now(GPR_CLOCK_REALTIME), deadline) < 0);
+  // After the backend is restarted, the child policy will eventually be READY,
+  // and we will exit fallback mode.
+  StartBackend(1);
+  WaitForBackend(1);
+  // We have exited fallback mode, so calls will go to the child policy
+  // exclusively.
+  CheckRpcSendOk(100);
+  EXPECT_EQ(0U, backends_[0]->service_.request_count());
+  EXPECT_EQ(100U, backends_[1]->service_.request_count());
+}
+
+TEST_F(SingleBalancerTest, BackendsRestart) {
+  SetNextResolution({}, kDefaultServiceConfig_.c_str());
+  SetNextResolutionForLbChannelAllBalancers();
+  ScheduleResponseForBalancer(
+      0, BalancerServiceImpl::BuildResponseForBackends(GetBackendPorts(), {}),
+      0);
+  WaitForAllBackends();
   // Stop backends.  RPCs should fail.
   ShutdownAllBackends();
   CheckRpcSendFailure();
   // Restart all backends.  RPCs should start succeeding again.
   StartAllBackends();
-  CheckRpcSendOk(1 /* times */, 1000 /* timeout_ms */,
+  CheckRpcSendOk(1 /* times */, 2000 /* timeout_ms */,
                  true /* wait_for_ready */);
 }
 
diff --git a/test/cpp/interop/interop_test.cc b/test/cpp/interop/interop_test.cc
index ae155b6..8e45b87 100644
--- a/test/cpp/interop/interop_test.cc
+++ b/test/cpp/interop/interop_test.cc
@@ -16,10 +16,6 @@
  *
  */
 
-#ifndef _POSIX_SOURCE
-#define _POSIX_SOURCE
-#endif
-
 #include <assert.h>
 #include <signal.h>
 #include <stdio.h>
diff --git a/test/cpp/microbenchmarks/bm_alarm.cc b/test/cpp/microbenchmarks/bm_alarm.cc
index 64aad64..d95771a 100644
--- a/test/cpp/microbenchmarks/bm_alarm.cc
+++ b/test/cpp/microbenchmarks/bm_alarm.cc
@@ -30,8 +30,6 @@
 namespace grpc {
 namespace testing {
 
-auto& force_library_initialization = Library::get();
-
 static void BM_Alarm_Tag_Immediate(benchmark::State& state) {
   TrackCounters track_counters;
   CompletionQueue cq;
@@ -57,6 +55,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_arena.cc b/test/cpp/microbenchmarks/bm_arena.cc
index b97c954..c3ded0d 100644
--- a/test/cpp/microbenchmarks/bm_arena.cc
+++ b/test/cpp/microbenchmarks/bm_arena.cc
@@ -19,40 +19,42 @@
 /* Benchmark arenas */
 
 #include <benchmark/benchmark.h>
-#include "src/core/lib/gpr/arena.h"
+#include "src/core/lib/gprpp/arena.h"
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
+using grpc_core::Arena;
+
 static void BM_Arena_NoOp(benchmark::State& state) {
   while (state.KeepRunning()) {
-    gpr_arena_destroy(gpr_arena_create(state.range(0)));
+    Arena::Create(state.range(0))->Destroy();
   }
 }
 BENCHMARK(BM_Arena_NoOp)->Range(1, 1024 * 1024);
 
 static void BM_Arena_ManyAlloc(benchmark::State& state) {
-  gpr_arena* a = gpr_arena_create(state.range(0));
+  Arena* a = Arena::Create(state.range(0));
   const size_t realloc_after =
       1024 * 1024 * 1024 / ((state.range(1) + 15) & 0xffffff0u);
   while (state.KeepRunning()) {
-    gpr_arena_alloc(a, state.range(1));
+    a->Alloc(state.range(1));
     // periodically recreate arena to avoid OOM
     if (state.iterations() % realloc_after == 0) {
-      gpr_arena_destroy(a);
-      a = gpr_arena_create(state.range(0));
+      a->Destroy();
+      a = Arena::Create(state.range(0));
     }
   }
-  gpr_arena_destroy(a);
+  a->Destroy();
 }
 BENCHMARK(BM_Arena_ManyAlloc)->Ranges({{1, 1024 * 1024}, {1, 32 * 1024}});
 
 static void BM_Arena_Batch(benchmark::State& state) {
   while (state.KeepRunning()) {
-    gpr_arena* a = gpr_arena_create(state.range(0));
+    Arena* a = Arena::Create(state.range(0));
     for (int i = 0; i < state.range(1); i++) {
-      gpr_arena_alloc(a, state.range(2));
+      a->Alloc(state.range(2));
     }
-    gpr_arena_destroy(a);
+    a->Destroy();
   }
 }
 BENCHMARK(BM_Arena_Batch)->Ranges({{1, 64 * 1024}, {1, 64}, {1, 1024}});
diff --git a/test/cpp/microbenchmarks/bm_byte_buffer.cc b/test/cpp/microbenchmarks/bm_byte_buffer.cc
index 644c27c..595cc73 100644
--- a/test/cpp/microbenchmarks/bm_byte_buffer.cc
+++ b/test/cpp/microbenchmarks/bm_byte_buffer.cc
@@ -30,7 +30,6 @@
 namespace testing {
 
 static void BM_ByteBuffer_Copy(benchmark::State& state) {
-  Library::get();
   int num_slices = state.range(0);
   size_t slice_size = state.range(1);
   std::vector<grpc::Slice> slices;
@@ -48,7 +47,6 @@
 BENCHMARK(BM_ByteBuffer_Copy)->Ranges({{1, 64}, {1, 1024 * 1024}});
 
 static void BM_ByteBufferReader_Next(benchmark::State& state) {
-  Library::get();
   const int num_slices = state.range(0);
   constexpr size_t kSliceSize = 16;
   std::vector<grpc_slice> slices;
@@ -82,7 +80,6 @@
 BENCHMARK(BM_ByteBufferReader_Next)->Ranges({{64 * 1024, 1024 * 1024}});
 
 static void BM_ByteBufferReader_Peek(benchmark::State& state) {
-  Library::get();
   const int num_slices = state.range(0);
   constexpr size_t kSliceSize = 16;
   std::vector<grpc_slice> slices;
@@ -125,6 +122,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_call_create.cc b/test/cpp/microbenchmarks/bm_call_create.cc
index e84999b..3bd1464 100644
--- a/test/cpp/microbenchmarks/bm_call_create.cc
+++ b/test/cpp/microbenchmarks/bm_call_create.cc
@@ -47,8 +47,6 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
-auto& force_library_initialization = Library::get();
-
 void BM_Zalloc(benchmark::State& state) {
   // speed of light for call creation is zalloc, so benchmark a few interesting
   // sizes
@@ -405,7 +403,7 @@
 /* implementation of grpc_transport_init_stream */
 int InitStream(grpc_transport* self, grpc_stream* stream,
                grpc_stream_refcount* refcount, const void* server_data,
-               gpr_arena* arena) {
+               grpc_core::Arena* arena) {
   return 0;
 }
 
@@ -540,7 +538,7 @@
                                    method,
                                    start_time,
                                    deadline,
-                                   gpr_arena_create(kArenaSize),
+                                   grpc_core::Arena::Create(kArenaSize),
                                    nullptr};
   while (state.KeepRunning()) {
     GPR_TIMER_SCOPE("BenchmarkCycle", 0);
@@ -552,11 +550,11 @@
     grpc_core::ExecCtx::Get()->Flush();
     // recreate arena every 64k iterations to avoid oom
     if (0 == (state.iterations() & 0xffff)) {
-      gpr_arena_destroy(call_args.arena);
-      call_args.arena = gpr_arena_create(kArenaSize);
+      call_args.arena->Destroy();
+      call_args.arena = grpc_core::Arena::Create(kArenaSize);
     }
   }
-  gpr_arena_destroy(call_args.arena);
+  call_args.arena->Destroy();
   grpc_channel_stack_destroy(channel_stack);
   grpc_core::ExecCtx::Get()->Flush();
 
@@ -609,7 +607,7 @@
 namespace isolated_call_filter {
 
 typedef struct {
-  grpc_call_combiner* call_combiner;
+  grpc_core::CallCombiner* call_combiner;
 } call_data;
 
 static void StartTransportStreamOp(grpc_call_element* elem,
@@ -823,6 +821,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_channel.cc b/test/cpp/microbenchmarks/bm_channel.cc
index 15ac997..88856c3 100644
--- a/test/cpp/microbenchmarks/bm_channel.cc
+++ b/test/cpp/microbenchmarks/bm_channel.cc
@@ -23,8 +23,6 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
-auto& force_library_initialization = Library::get();
-
 class ChannelDestroyerFixture {
  public:
   ChannelDestroyerFixture() {}
@@ -83,6 +81,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_chttp2_hpack.cc b/test/cpp/microbenchmarks/bm_chttp2_hpack.cc
index 85d233d..d0ed1da 100644
--- a/test/cpp/microbenchmarks/bm_chttp2_hpack.cc
+++ b/test/cpp/microbenchmarks/bm_chttp2_hpack.cc
@@ -36,8 +36,6 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
-auto& force_library_initialization = Library::get();
-
 static grpc_slice MakeSlice(std::vector<uint8_t> bytes) {
   grpc_slice s = grpc_slice_malloc(bytes.size());
   uint8_t* p = GRPC_SLICE_START_PTR(s);
@@ -458,7 +456,7 @@
   grpc_chttp2_hpack_parser p;
   grpc_chttp2_hpack_parser_init(&p);
   const int kArenaSize = 4096 * 4096;
-  p.on_header_user_data = gpr_arena_create(kArenaSize);
+  p.on_header_user_data = grpc_core::Arena::Create(kArenaSize);
   p.on_header = OnHeader;
   for (auto slice : init_slices) {
     GPR_ASSERT(GRPC_ERROR_NONE == grpc_chttp2_hpack_parser_parse(&p, slice));
@@ -470,12 +468,12 @@
     grpc_core::ExecCtx::Get()->Flush();
     // Recreate arena every 4k iterations to avoid oom
     if (0 == (state.iterations() & 0xfff)) {
-      gpr_arena_destroy((gpr_arena*)p.on_header_user_data);
-      p.on_header_user_data = gpr_arena_create(kArenaSize);
+      static_cast<grpc_core::Arena*>(p.on_header_user_data)->Destroy();
+      p.on_header_user_data = grpc_core::Arena::Create(kArenaSize);
     }
   }
   // Clean up
-  gpr_arena_destroy((gpr_arena*)p.on_header_user_data);
+  static_cast<grpc_core::Arena*>(p.on_header_user_data)->Destroy();
   for (auto slice : init_slices) grpc_slice_unref(slice);
   for (auto slice : benchmark_slices) grpc_slice_unref(slice);
   grpc_chttp2_hpack_parser_destroy(&p);
@@ -778,7 +776,8 @@
 // Benchmark the current on_initial_header implementation
 static void OnInitialHeader(void* user_data, grpc_mdelem md) {
   // Setup for benchmark. This will bloat the absolute values of this benchmark
-  grpc_chttp2_incoming_metadata_buffer buffer((gpr_arena*)user_data);
+  grpc_chttp2_incoming_metadata_buffer buffer(
+      static_cast<grpc_core::Arena*>(user_data));
   bool seen_error = false;
 
   // Below here is the code we actually care about benchmarking
@@ -927,6 +926,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_chttp2_transport.cc b/test/cpp/microbenchmarks/bm_chttp2_transport.cc
index baa6da3..0550458 100644
--- a/test/cpp/microbenchmarks/bm_chttp2_transport.cc
+++ b/test/cpp/microbenchmarks/bm_chttp2_transport.cc
@@ -36,8 +36,6 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
-auto& force_library_initialization = Library::get();
-
 ////////////////////////////////////////////////////////////////////////////////
 // Helper classes
 //
@@ -57,7 +55,8 @@
                                                    get_fd,
                                                    can_track_err};
     grpc_endpoint::vtable = &my_vtable;
-    ru_ = grpc_resource_user_create(Library::get().rq(), "dummy_endpoint");
+    ru_ = grpc_resource_user_create(LibraryInitializer::get().rq(),
+                                    "dummy_endpoint");
   }
 
   void PushInput(grpc_slice slice) {
@@ -193,13 +192,13 @@
   Stream(Fixture* f) : f_(f) {
     stream_size_ = grpc_transport_stream_size(f->transport());
     stream_ = gpr_malloc(stream_size_);
-    arena_ = gpr_arena_create(4096);
+    arena_ = grpc_core::Arena::Create(4096);
   }
 
   ~Stream() {
     gpr_event_wait(&done_, gpr_inf_future(GPR_CLOCK_REALTIME));
     gpr_free(stream_);
-    gpr_arena_destroy(arena_);
+    arena_->Destroy();
   }
 
   void Init(benchmark::State& state) {
@@ -208,8 +207,8 @@
     gpr_event_init(&done_);
     memset(stream_, 0, stream_size_);
     if ((state.iterations() & 0xffff) == 0) {
-      gpr_arena_destroy(arena_);
-      arena_ = gpr_arena_create(4096);
+      arena_->Destroy();
+      arena_ = grpc_core::Arena::Create(4096);
     }
     grpc_transport_init_stream(f_->transport(),
                                static_cast<grpc_stream*>(stream_), &refcount_,
@@ -245,7 +244,7 @@
 
   Fixture* f_;
   grpc_stream_refcount refcount_;
-  gpr_arena* arena_;
+  grpc_core::Arena* arena_;
   size_t stream_size_;
   void* stream_;
   grpc_closure* destroy_closure_ = nullptr;
@@ -642,6 +641,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_closure.cc b/test/cpp/microbenchmarks/bm_closure.cc
index e1f1e92..84b1c53 100644
--- a/test/cpp/microbenchmarks/bm_closure.cc
+++ b/test/cpp/microbenchmarks/bm_closure.cc
@@ -30,8 +30,6 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
-auto& force_library_initialization = Library::get();
-
 static void BM_NoOpExecCtx(benchmark::State& state) {
   TrackCounters track_counters;
   while (state.KeepRunning()) {
@@ -425,6 +423,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_cq.cc b/test/cpp/microbenchmarks/bm_cq.cc
index a7cb939..263314d 100644
--- a/test/cpp/microbenchmarks/bm_cq.cc
+++ b/test/cpp/microbenchmarks/bm_cq.cc
@@ -32,8 +32,6 @@
 namespace grpc {
 namespace testing {
 
-auto& force_library_initialization = Library::get();
-
 static void BM_CreateDestroyCpp(benchmark::State& state) {
   TrackCounters track_counters;
   while (state.KeepRunning()) {
@@ -156,6 +154,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_cq_multiple_threads.cc b/test/cpp/microbenchmarks/bm_cq_multiple_threads.cc
index 5445535..329eaf2 100644
--- a/test/cpp/microbenchmarks/bm_cq_multiple_threads.cc
+++ b/test/cpp/microbenchmarks/bm_cq_multiple_threads.cc
@@ -138,7 +138,7 @@
  Setup:
   The benchmark framework ensures that none of the threads proceed beyond the
   state.KeepRunning() call unless all the threads have called state.keepRunning
-  atleast once.  So it is safe to do the initialization in one of the threads
+  at least once.  So it is safe to do the initialization in one of the threads
   before state.KeepRunning() is called.
 
  Teardown:
diff --git a/test/cpp/microbenchmarks/bm_error.cc b/test/cpp/microbenchmarks/bm_error.cc
index ae557a5..a71817f 100644
--- a/test/cpp/microbenchmarks/bm_error.cc
+++ b/test/cpp/microbenchmarks/bm_error.cc
@@ -27,8 +27,6 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
-auto& force_library_initialization = Library::get();
-
 class ErrorDeleter {
  public:
   void operator()(grpc_error* error) { GRPC_ERROR_UNREF(error); }
@@ -318,6 +316,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_fullstack_streaming_ping_pong.cc b/test/cpp/microbenchmarks/bm_fullstack_streaming_ping_pong.cc
index 34df77a..60ca9a0 100644
--- a/test/cpp/microbenchmarks/bm_fullstack_streaming_ping_pong.cc
+++ b/test/cpp/microbenchmarks/bm_fullstack_streaming_ping_pong.cc
@@ -24,9 +24,6 @@
 namespace grpc {
 namespace testing {
 
-// force library initialization
-auto& force_library_initialization = Library::get();
-
 /*******************************************************************************
  * CONFIGURATIONS
  */
@@ -122,6 +119,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_fullstack_streaming_pump.cc b/test/cpp/microbenchmarks/bm_fullstack_streaming_pump.cc
index da98f3c..d4533e6 100644
--- a/test/cpp/microbenchmarks/bm_fullstack_streaming_pump.cc
+++ b/test/cpp/microbenchmarks/bm_fullstack_streaming_pump.cc
@@ -28,9 +28,6 @@
  * CONFIGURATIONS
  */
 
-// force library initialization
-auto& force_library_initialization = Library::get();
-
 BENCHMARK_TEMPLATE(BM_PumpStreamClientToServer, TCP)
     ->Range(0, 128 * 1024 * 1024);
 BENCHMARK_TEMPLATE(BM_PumpStreamClientToServer, UDS)
@@ -72,6 +69,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_fullstack_trickle.cc b/test/cpp/microbenchmarks/bm_fullstack_trickle.cc
index 1af92d2..2d733bc 100644
--- a/test/cpp/microbenchmarks/bm_fullstack_trickle.cc
+++ b/test/cpp/microbenchmarks/bm_fullstack_trickle.cc
@@ -214,8 +214,8 @@
   static grpc_endpoint_pair MakeEndpoints(size_t kilobits,
                                           grpc_passthru_endpoint_stats* stats) {
     grpc_endpoint_pair p;
-    grpc_passthru_endpoint_create(&p.client, &p.server, Library::get().rq(),
-                                  stats);
+    grpc_passthru_endpoint_create(&p.client, &p.server,
+                                  LibraryInitializer::get().rq(), stats);
     double bytes_per_second = 125.0 * kilobits;
     p.client = grpc_trickle_endpoint_create(p.client, bytes_per_second);
     p.server = grpc_trickle_endpoint_create(p.server, bytes_per_second);
@@ -235,9 +235,6 @@
   }
 };
 
-// force library initialization
-auto& force_library_initialization = Library::get();
-
 static void TrickleCQNext(TrickledCHTTP2* fixture, void** t, bool* ok,
                           int64_t iteration) {
   while (true) {
@@ -465,6 +462,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   grpc_timer_manager_set_threading(false);
diff --git a/test/cpp/microbenchmarks/bm_fullstack_unary_ping_pong.cc b/test/cpp/microbenchmarks/bm_fullstack_unary_ping_pong.cc
index d4bd58b..3e8f934 100644
--- a/test/cpp/microbenchmarks/bm_fullstack_unary_ping_pong.cc
+++ b/test/cpp/microbenchmarks/bm_fullstack_unary_ping_pong.cc
@@ -24,9 +24,6 @@
 namespace grpc {
 namespace testing {
 
-// force library initialization
-auto& force_library_initialization = Library::get();
-
 /*******************************************************************************
  * CONFIGURATIONS
  */
@@ -174,6 +171,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_metadata.cc b/test/cpp/microbenchmarks/bm_metadata.cc
index 553b33c..ff8fe54 100644
--- a/test/cpp/microbenchmarks/bm_metadata.cc
+++ b/test/cpp/microbenchmarks/bm_metadata.cc
@@ -27,8 +27,6 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 #include "test/cpp/util/test_config.h"
 
-auto& force_library_initialization = Library::get();
-
 static void BM_SliceFromStatic(benchmark::State& state) {
   TrackCounters track_counters;
   while (state.KeepRunning()) {
@@ -298,6 +296,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_pollset.cc b/test/cpp/microbenchmarks/bm_pollset.cc
index 050c7f7..f8e36f1 100644
--- a/test/cpp/microbenchmarks/bm_pollset.cc
+++ b/test/cpp/microbenchmarks/bm_pollset.cc
@@ -40,8 +40,6 @@
 #include <unistd.h>
 #endif
 
-auto& force_library_initialization = Library::get();
-
 static void shutdown_ps(void* ps, grpc_error* error) {
   grpc_pollset_destroy(static_cast<grpc_pollset*>(ps));
 }
@@ -264,6 +262,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/bm_timer.cc b/test/cpp/microbenchmarks/bm_timer.cc
index f5a4112..53aaead 100644
--- a/test/cpp/microbenchmarks/bm_timer.cc
+++ b/test/cpp/microbenchmarks/bm_timer.cc
@@ -32,8 +32,6 @@
 namespace grpc {
 namespace testing {
 
-auto& force_library_initialization = Library::get();
-
 struct TimerClosure {
   grpc_timer timer;
   grpc_closure closure;
@@ -111,6 +109,7 @@
 }  // namespace benchmark
 
 int main(int argc, char** argv) {
+  LibraryInitializer libInit;
   ::benchmark::Initialize(&argc, argv);
   ::grpc::testing::InitTest(&argc, &argv, false);
   benchmark::RunTheBenchmarksNamespaced();
diff --git a/test/cpp/microbenchmarks/fullstack_fixtures.h b/test/cpp/microbenchmarks/fullstack_fixtures.h
index 6bbf553..4d60e97 100644
--- a/test/cpp/microbenchmarks/fullstack_fixtures.h
+++ b/test/cpp/microbenchmarks/fullstack_fixtures.h
@@ -86,8 +86,8 @@
     ChannelArguments args;
     config.ApplyCommonChannelArguments(&args);
     if (address.length() > 0) {
-      channel_ =
-          CreateCustomChannel(address, InsecureChannelCredentials(), args);
+      channel_ = ::grpc::CreateCustomChannel(
+          address, InsecureChannelCredentials(), args);
     } else {
       channel_ = server_->InProcessChannel(args);
     }
@@ -218,7 +218,7 @@
           "target", &c_args, GRPC_CLIENT_DIRECT_CHANNEL, client_transport_);
       grpc_chttp2_transport_start_reading(client_transport_, nullptr, nullptr);
 
-      channel_ = CreateChannelInternal(
+      channel_ = ::grpc::CreateChannelInternal(
           "", channel,
           std::vector<std::unique_ptr<
               experimental::ClientInterceptorFactoryInterface>>());
@@ -299,8 +299,8 @@
 
   static grpc_endpoint_pair MakeEndpoints(grpc_passthru_endpoint_stats* stats) {
     grpc_endpoint_pair p;
-    grpc_passthru_endpoint_create(&p.client, &p.server, Library::get().rq(),
-                                  stats);
+    grpc_passthru_endpoint_create(&p.client, &p.server,
+                                  LibraryInitializer::get().rq(), stats);
     return p;
   }
 };
diff --git a/test/cpp/microbenchmarks/helpers.cc b/test/cpp/microbenchmarks/helpers.cc
index d4070de..7d78e21 100644
--- a/test/cpp/microbenchmarks/helpers.cc
+++ b/test/cpp/microbenchmarks/helpers.cc
@@ -21,8 +21,12 @@
 #include "test/cpp/microbenchmarks/helpers.h"
 
 static grpc::internal::GrpcLibraryInitializer g_gli_initializer;
+static LibraryInitializer* g_libraryInitializer;
 
-Library::Library() {
+LibraryInitializer::LibraryInitializer() {
+  GPR_ASSERT(g_libraryInitializer == nullptr);
+  g_libraryInitializer = this;
+
   g_gli_initializer.summon();
 #ifdef GPR_LOW_LEVEL_COUNTERS
   grpc_memory_counters_init();
@@ -31,6 +35,17 @@
   rq_ = grpc_resource_quota_create("bm");
 }
 
+LibraryInitializer::~LibraryInitializer() {
+  g_libraryInitializer = nullptr;
+  init_lib_.shutdown();
+  grpc_resource_quota_unref(rq_);
+}
+
+LibraryInitializer& LibraryInitializer::get() {
+  GPR_ASSERT(g_libraryInitializer != nullptr);
+  return *g_libraryInitializer;
+}
+
 void TrackCounters::Finish(benchmark::State& state) {
   std::ostringstream out;
   for (const auto& l : labels_) {
diff --git a/test/cpp/microbenchmarks/helpers.h b/test/cpp/microbenchmarks/helpers.h
index 770966a..b712c85 100644
--- a/test/cpp/microbenchmarks/helpers.h
+++ b/test/cpp/microbenchmarks/helpers.h
@@ -29,20 +29,16 @@
 #include <benchmark/benchmark.h>
 #include <grpcpp/impl/grpc_library.h>
 
-class Library {
+class LibraryInitializer {
  public:
-  static Library& get() {
-    static Library lib;
-    return lib;
-  }
+  LibraryInitializer();
+  ~LibraryInitializer();
 
   grpc_resource_quota* rq() { return rq_; }
 
+  static LibraryInitializer& get();
+
  private:
-  Library();
-
-  ~Library() { init_lib_.shutdown(); }
-
   grpc::internal::GrpcLibrary init_lib_;
   grpc_resource_quota* rq_;
 };
diff --git a/test/cpp/naming/BUILD b/test/cpp/naming/BUILD
index 58e7048..7db435a 100644
--- a/test/cpp/naming/BUILD
+++ b/test/cpp/naming/BUILD
@@ -22,7 +22,7 @@
 
 licenses(["notice"])  # Apache v2
 
-load("//bazel:grpc_build_system.bzl", "grpc_py_binary", "grpc_cc_test")
+load("//bazel:grpc_build_system.bzl", "grpc_py_binary", "grpc_cc_test", "grpc_cc_library")
 load(":generate_resolver_component_tests.bzl", "generate_resolver_component_tests")
 
 # Meant to be invoked only through the top-level shell script driver.
@@ -39,6 +39,7 @@
     srcs = ["cancel_ares_query_test.cc"],
     external_deps = ["gmock"],
     deps = [
+        ":dns_test_util",
         "//:gpr",
         "//:grpc",
         "//:grpc++",
@@ -49,4 +50,14 @@
     ],
 )
 
+grpc_cc_library(
+    name = "dns_test_util",
+    hdrs = ["dns_test_util.h"],
+    srcs = ["dns_test_util.cc"],
+    deps = [
+        "//:gpr",
+        "//:grpc",
+    ],
+)
+
 generate_resolver_component_tests()
diff --git a/test/cpp/naming/address_sorting_test.cc b/test/cpp/naming/address_sorting_test.cc
index 78ad49e..affc75b 100644
--- a/test/cpp/naming/address_sorting_test.cc
+++ b/test/cpp/naming/address_sorting_test.cc
@@ -36,10 +36,10 @@
 #include "src/core/ext/filters/client_channel/client_channel.h"
 #include "src/core/ext/filters/client_channel/resolver.h"
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/iomgr/combiner.h"
@@ -52,6 +52,7 @@
 
 #ifndef GPR_WINDOWS
 #include <arpa/inet.h>
+#include <netinet/in.h>
 #include <sys/socket.h>
 #endif
 
@@ -828,13 +829,13 @@
 }  // namespace
 
 int main(int argc, char** argv) {
-  char* resolver = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (resolver == nullptr || strlen(resolver) == 0) {
-    gpr_setenv("GRPC_DNS_RESOLVER", "ares");
-  } else if (strcmp("ares", resolver)) {
-    gpr_log(GPR_INFO, "GRPC_DNS_RESOLVER != ares: %s.", resolver);
+  grpc_core::UniquePtr<char> resolver =
+      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
+  if (strlen(resolver.get()) == 0) {
+    GPR_GLOBAL_CONFIG_SET(grpc_dns_resolver, "ares");
+  } else if (strcmp("ares", resolver.get())) {
+    gpr_log(GPR_INFO, "GRPC_DNS_RESOLVER != ares: %s.", resolver.get());
   }
-  gpr_free(resolver);
   grpc::testing::TestEnvironment env(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   auto result = RUN_ALL_TESTS();
diff --git a/test/cpp/naming/cancel_ares_query_test.cc b/test/cpp/naming/cancel_ares_query_test.cc
index bcf96aa..667011a 100644
--- a/test/cpp/naming/cancel_ares_query_test.cc
+++ b/test/cpp/naming/cancel_ares_query_test.cc
@@ -29,10 +29,10 @@
 #include <grpc/support/time.h>
 #include "include/grpc/support/string_util.h"
 #include "src/core/ext/filters/client_channel/resolver.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/debug/stats.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/orphanable.h"
@@ -44,6 +44,7 @@
 #include "test/core/util/cmdline.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
+#include "test/cpp/naming/dns_test_util.h"
 
 #ifdef GPR_WINDOWS
 #include "src/core/lib/iomgr/sockaddr_windows.h"
@@ -76,36 +77,6 @@
   grpc_completion_queue_destroy(cq);
 }
 
-class FakeNonResponsiveDNSServer {
- public:
-  FakeNonResponsiveDNSServer(int port) {
-    socket_ = socket(AF_INET6, SOCK_DGRAM, 0);
-    if (socket_ == BAD_SOCKET_RETURN_VAL) {
-      gpr_log(GPR_DEBUG, "Failed to create UDP ipv6 socket");
-      abort();
-    }
-    sockaddr_in6 addr;
-    memset(&addr, 0, sizeof(addr));
-    addr.sin6_family = AF_INET6;
-    addr.sin6_port = htons(port);
-    ((char*)&addr.sin6_addr)[15] = 1;
-    if (bind(socket_, (const sockaddr*)&addr, sizeof(addr)) != 0) {
-      gpr_log(GPR_DEBUG, "Failed to bind UDP ipv6 socket to [::1]:%d", port);
-      abort();
-    }
-  }
-  ~FakeNonResponsiveDNSServer() {
-#ifdef GPR_WINDOWS
-    closesocket(socket_);
-#else
-    close(socket_);
-#endif
-  }
-
- private:
-  int socket_;
-};
-
 struct ArgsStruct {
   gpr_atm done_atm;
   gpr_mu* mu;
@@ -184,7 +155,7 @@
 
 void TestCancelActiveDNSQuery(ArgsStruct* args) {
   int fake_dns_port = grpc_pick_unused_port_or_die();
-  FakeNonResponsiveDNSServer fake_dns_server(fake_dns_port);
+  grpc::testing::FakeNonResponsiveDNSServer fake_dns_server(fake_dns_port);
   char* client_target;
   GPR_ASSERT(gpr_asprintf(
       &client_target,
@@ -282,7 +253,7 @@
     cancellation_test_query_timeout_setting query_timeout_setting) {
   // Start up fake non responsive DNS server
   int fake_dns_port = grpc_pick_unused_port_or_die();
-  FakeNonResponsiveDNSServer fake_dns_server(fake_dns_port);
+  grpc::testing::FakeNonResponsiveDNSServer fake_dns_server(fake_dns_port);
   // Create a call that will try to use the fake DNS server
   char* client_target = nullptr;
   GPR_ASSERT(gpr_asprintf(
@@ -403,7 +374,7 @@
 int main(int argc, char** argv) {
   grpc::testing::TestEnvironment env(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
-  gpr_setenv("GRPC_DNS_RESOLVER", "ares");
+  GPR_GLOBAL_CONFIG_SET(grpc_dns_resolver, "ares");
   // Sanity check the time that it takes to run the test
   // including the teardown time (the teardown
   // part of the test involves cancelling the DNS query,
diff --git a/test/cpp/naming/dns_test_util.cc b/test/cpp/naming/dns_test_util.cc
new file mode 100644
index 0000000..1380d0a
--- /dev/null
+++ b/test/cpp/naming/dns_test_util.cc
@@ -0,0 +1,97 @@
+/*
+ *
+ * Copyright 2015 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "test/cpp/naming/dns_test_util.h"
+
+#ifdef GPR_WINDOWS
+#include "src/core/lib/iomgr/sockaddr_windows.h"
+#include "src/core/lib/iomgr/socket_windows.h"
+#define BAD_SOCKET_RETURN_VAL INVALID_SOCKET
+#else
+#include "src/core/lib/iomgr/sockaddr_posix.h"
+#define BAD_SOCKET_RETURN_VAL -1
+#endif
+
+namespace grpc {
+namespace testing {
+
+FakeNonResponsiveDNSServer::FakeNonResponsiveDNSServer(int port) {
+  udp_socket_ = socket(AF_INET6, SOCK_DGRAM, 0);
+  tcp_socket_ = socket(AF_INET6, SOCK_STREAM, 0);
+  if (udp_socket_ == BAD_SOCKET_RETURN_VAL) {
+    gpr_log(GPR_DEBUG, "Failed to create UDP ipv6 socket");
+    abort();
+  }
+  if (tcp_socket_ == BAD_SOCKET_RETURN_VAL) {
+    gpr_log(GPR_DEBUG, "Failed to create TCP ipv6 socket");
+    abort();
+  }
+  sockaddr_in6 addr;
+  memset(&addr, 0, sizeof(addr));
+  addr.sin6_family = AF_INET6;
+  addr.sin6_port = htons(port);
+  ((char*)&addr.sin6_addr)[15] = 1;
+  if (bind(udp_socket_, (const sockaddr*)&addr, sizeof(addr)) != 0) {
+    gpr_log(GPR_DEBUG, "Failed to bind UDP ipv6 socket to [::1]:%d", port);
+    abort();
+  }
+#ifdef GPR_WINDOWS
+  char val = 1;
+  if (setsockopt(tcp_socket_, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) ==
+      SOCKET_ERROR) {
+    gpr_log(GPR_DEBUG,
+            "Failed to set SO_REUSEADDR on TCP ipv6 socket to [::1]:%d", port);
+    abort();
+  }
+#else
+  int val = 1;
+  if (setsockopt(tcp_socket_, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) !=
+      0) {
+    gpr_log(GPR_DEBUG,
+            "Failed to set SO_REUSEADDR on TCP ipv6 socket to [::1]:%d", port);
+    abort();
+  }
+#endif
+  if (bind(tcp_socket_, (const sockaddr*)&addr, sizeof(addr)) != 0) {
+    gpr_log(GPR_DEBUG, "Failed to bind TCP ipv6 socket to [::1]:%d", port);
+    abort();
+  }
+  if (listen(tcp_socket_, 100)) {
+    gpr_log(GPR_DEBUG, "Failed to listen on TCP ipv6 socket to [::1]:%d", port);
+    abort();
+  }
+}
+
+FakeNonResponsiveDNSServer::~FakeNonResponsiveDNSServer() {
+#ifdef GPR_WINDOWS
+  closesocket(udp_socket_);
+  closesocket(tcp_socket_);
+#else
+  close(udp_socket_);
+  close(tcp_socket_);
+#endif
+}
+
+}  // namespace testing
+}  // namespace grpc
diff --git a/test/cpp/naming/dns_test_util.h b/test/cpp/naming/dns_test_util.h
new file mode 100644
index 0000000..d5e5099
--- /dev/null
+++ b/test/cpp/naming/dns_test_util.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * 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_DNS_TEST_UTIL_H
+#define GRPC_DNS_TEST_UTIL_H
+
+namespace grpc {
+namespace testing {
+
+class FakeNonResponsiveDNSServer {
+ public:
+  explicit FakeNonResponsiveDNSServer(int port);
+  virtual ~FakeNonResponsiveDNSServer();
+
+ private:
+  int udp_socket_;
+  int tcp_socket_;
+};
+
+}  // namespace testing
+}  // namespace grpc
+
+#endif /* GRPC_DNS_TEST_UTIL_H */
diff --git a/test/cpp/naming/gen_build_yaml.py b/test/cpp/naming/gen_build_yaml.py
index 9bf5ae9..7cbf916 100755
--- a/test/cpp/naming/gen_build_yaml.py
+++ b/test/cpp/naming/gen_build_yaml.py
@@ -47,9 +47,11 @@
              _build_expected_addrs_cmd_arg(test_case['expected_addrs'])),
             ('expected_chosen_service_config',
              (test_case['expected_chosen_service_config'] or '')),
+            ('expected_service_config_error', (test_case['expected_service_config_error'] or '')),
             ('expected_lb_policy', (test_case['expected_lb_policy'] or '')),
             ('enable_srv_queries', test_case['enable_srv_queries']),
             ('enable_txt_queries', test_case['enable_txt_queries']),
+            ('inject_broken_nameserver_list', test_case['inject_broken_nameserver_list']),
         ],
     })
   return out
@@ -72,6 +74,7 @@
               'src': ['test/cpp/naming/resolver_component_test.cc'],
               'platforms': ['linux', 'posix', 'mac', 'windows'],
               'deps': [
+                  'dns_test_util',
                   'grpc++_test_util' + unsecure_build_config_suffix,
                   'grpc_test_util' + unsecure_build_config_suffix,
                   'grpc++' + unsecure_build_config_suffix,
@@ -130,6 +133,7 @@
           'src': ['test/cpp/naming/cancel_ares_query_test.cc'],
           'platforms': ['linux', 'posix', 'mac', 'windows'],
           'deps': [
+              'dns_test_util',
               'grpc++_test_util',
               'grpc_test_util',
               'grpc++',
diff --git a/test/cpp/naming/generate_resolver_component_tests.bzl b/test/cpp/naming/generate_resolver_component_tests.bzl
index 5891767..bcc62f6 100755
--- a/test/cpp/naming/generate_resolver_component_tests.bzl
+++ b/test/cpp/naming/generate_resolver_component_tests.bzl
@@ -46,6 +46,7 @@
             "gmock",
         ],
         deps = [
+            ":dns_test_util",
             "//test/cpp/util:test_util%s" % unsecure_build_config_suffix,
             "//test/core/util:grpc_test_util%s" % unsecure_build_config_suffix,
             "//:grpc++%s" % unsecure_build_config_suffix,
diff --git a/test/cpp/naming/resolver_component_test.cc b/test/cpp/naming/resolver_component_test.cc
index 398822d..6cea814 100644
--- a/test/cpp/naming/resolver_component_test.cc
+++ b/test/cpp/naming/resolver_component_test.cc
@@ -39,12 +39,13 @@
 #include "test/cpp/util/test_config.h"
 
 #include "src/core/ext/filters/client_channel/client_channel.h"
+#include "src/core/ext/filters/client_channel/parse_address.h"
 #include "src/core/ext/filters/client_channel/resolver.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h"
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/orphanable.h"
@@ -53,9 +54,12 @@
 #include "src/core/lib/iomgr/iomgr.h"
 #include "src/core/lib/iomgr/resolve_address.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
+#include "src/core/lib/iomgr/socket_utils.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
+#include "test/cpp/naming/dns_test_util.h"
+
 // TODO: pull in different headers when enabling this
 // test on windows. Also set BAD_SOCKET_RETURN_VAL
 // to INVALID_SOCKET on windows.
@@ -89,6 +93,8 @@
 DEFINE_string(expected_chosen_service_config, "",
               "Expected service config json string that gets chosen (no "
               "whitespace). Empty for none.");
+DEFINE_string(expected_service_config_error, "",
+              "Expected service config error. Empty for none.");
 DEFINE_string(
     local_dns_server_address, "",
     "Optional. This address is placed as the uri authority if present.");
@@ -106,6 +112,17 @@
     "generate "
     "the python script runner doesn't allow us to pass a gflags bool to this "
     "binary.");
+DEFINE_string(
+    inject_broken_nameserver_list, "",
+    "Whether or not to configure c-ares to use a broken nameserver list, in "
+    "which "
+    "the first nameserver in the list is non-responsive, but the second one "
+    "works, i.e "
+    "serves the expected DNS records; using for testing such a real scenario."
+    "It would be better if this arg could be bool, but the way that we "
+    "generate "
+    "the python script runner doesn't allow us to pass a gflags bool to this "
+    "binary.");
 DEFINE_string(expected_lb_policy, "",
               "Expected lb policy name that appears in resolver result channel "
               "arg. Empty for none.");
@@ -146,8 +163,9 @@
     expected_addrs = expected_addrs.substr(next_comma + 1, std::string::npos);
     // get the next is_balancer 'bool' associated with this address
     size_t next_semicolon = expected_addrs.find(';');
-    bool is_balancer =
-        gpr_is_true(expected_addrs.substr(0, next_semicolon).c_str());
+    bool is_balancer = false;
+    gpr_parse_bool_value(expected_addrs.substr(0, next_semicolon).c_str(),
+                         &is_balancer);
     out.emplace_back(GrpcLBAddress(next_addr, is_balancer));
     if (next_semicolon == std::string::npos) {
       break;
@@ -178,6 +196,7 @@
   grpc_channel_args* channel_args;
   vector<GrpcLBAddress> expected_addrs;
   std::string expected_service_config_string;
+  std::string expected_service_config_error;
   std::string expected_lb_policy;
 };
 
@@ -216,7 +235,10 @@
 }
 
 void PollPollsetUntilRequestDone(ArgsStruct* args) {
-  gpr_timespec deadline = NSecondDeadline(10);
+  // Use a 20-second timeout to give room for the tests that involve
+  // a non-responsive name server (c-ares uses a ~5 second query timeout
+  // for that server before succeeding with the healthy one).
+  gpr_timespec deadline = NSecondDeadline(20);
   while (true) {
     bool done = gpr_atm_acq_load(&args->done_atm) != 0;
     if (done) {
@@ -240,13 +262,19 @@
 }
 
 void CheckServiceConfigResultLocked(const char* service_config_json,
+                                    grpc_error* service_config_error,
                                     ArgsStruct* args) {
   if (args->expected_service_config_string != "") {
     GPR_ASSERT(service_config_json != nullptr);
     EXPECT_EQ(service_config_json, args->expected_service_config_string);
-  } else {
-    GPR_ASSERT(service_config_json == nullptr);
   }
+  if (args->expected_service_config_error == "") {
+    EXPECT_EQ(service_config_error, GRPC_ERROR_NONE);
+  } else {
+    EXPECT_THAT(grpc_error_string(service_config_error),
+                testing::HasSubstr(args->expected_service_config_error));
+  }
+  GRPC_ERROR_UNREF(service_config_error);
 }
 
 void CheckLBPolicyResultLocked(const grpc_channel_args* channel_args,
@@ -461,13 +489,58 @@
         result.service_config == nullptr
             ? nullptr
             : result.service_config->service_config_json();
-    CheckServiceConfigResultLocked(service_config_json, args);
+    CheckServiceConfigResultLocked(
+        service_config_json, GRPC_ERROR_REF(result.service_config_error), args);
     if (args->expected_service_config_string == "") {
       CheckLBPolicyResultLocked(result.args, args);
     }
   }
 };
 
+int g_fake_non_responsive_dns_server_port = -1;
+
+/* This function will configure any ares_channel created by the c-ares based
+ * resolver. This is useful to effectively mock /etc/resolv.conf settings
+ * (and equivalent on Windows), which unit tests don't have write permissions.
+ */
+void InjectBrokenNameServerList(ares_channel channel) {
+  struct ares_addr_port_node dns_server_addrs[2];
+  memset(dns_server_addrs, 0, sizeof(dns_server_addrs));
+  char* unused_host;
+  char* local_dns_server_port;
+  GPR_ASSERT(gpr_split_host_port(FLAGS_local_dns_server_address.c_str(),
+                                 &unused_host, &local_dns_server_port));
+  gpr_log(GPR_DEBUG,
+          "Injecting broken nameserver list. Bad server address:|[::1]:%d|. "
+          "Good server address:%s",
+          g_fake_non_responsive_dns_server_port,
+          FLAGS_local_dns_server_address.c_str());
+  // Put the non-responsive DNS server at the front of c-ares's nameserver list.
+  dns_server_addrs[0].family = AF_INET6;
+  ((char*)&dns_server_addrs[0].addr.addr6)[15] = 0x1;
+  dns_server_addrs[0].tcp_port = g_fake_non_responsive_dns_server_port;
+  dns_server_addrs[0].udp_port = g_fake_non_responsive_dns_server_port;
+  dns_server_addrs[0].next = &dns_server_addrs[1];
+  // Put the actual healthy DNS server after the first one. The expectation is
+  // that the resolver will timeout the query to the non-responsive DNS server
+  // and will skip over to this healthy DNS server, without causing any DNS
+  // resolution errors.
+  dns_server_addrs[1].family = AF_INET;
+  ((char*)&dns_server_addrs[1].addr.addr4)[0] = 0x7f;
+  ((char*)&dns_server_addrs[1].addr.addr4)[3] = 0x1;
+  dns_server_addrs[1].tcp_port = atoi(local_dns_server_port);
+  dns_server_addrs[1].udp_port = atoi(local_dns_server_port);
+  dns_server_addrs[1].next = nullptr;
+  GPR_ASSERT(ares_set_servers_ports(channel, dns_server_addrs) == ARES_SUCCESS);
+  gpr_free(local_dns_server_port);
+  gpr_free(unused_host);
+}
+
+void StartResolvingLocked(void* arg, grpc_error* unused) {
+  grpc_core::Resolver* r = static_cast<grpc_core::Resolver*>(arg);
+  r->StartLocked();
+}
+
 void RunResolvesRelevantRecordsTest(
     grpc_core::UniquePtr<grpc_core::Resolver::ResultHandler> (
         *CreateResultHandler)(ArgsStruct* args)) {
@@ -476,12 +549,33 @@
   ArgsInit(&args);
   args.expected_addrs = ParseExpectedAddrs(FLAGS_expected_addrs);
   args.expected_service_config_string = FLAGS_expected_chosen_service_config;
+  args.expected_service_config_error = FLAGS_expected_service_config_error;
   args.expected_lb_policy = FLAGS_expected_lb_policy;
   // maybe build the address with an authority
   char* whole_uri = nullptr;
-  GPR_ASSERT(gpr_asprintf(&whole_uri, "dns://%s/%s",
-                          FLAGS_local_dns_server_address.c_str(),
-                          FLAGS_target_name.c_str()));
+  gpr_log(GPR_DEBUG,
+          "resolver_component_test: --inject_broken_nameserver_list: %s",
+          FLAGS_inject_broken_nameserver_list.c_str());
+  grpc_core::UniquePtr<grpc::testing::FakeNonResponsiveDNSServer>
+      fake_non_responsive_dns_server;
+  if (FLAGS_inject_broken_nameserver_list == "True") {
+    g_fake_non_responsive_dns_server_port = grpc_pick_unused_port_or_die();
+    fake_non_responsive_dns_server.reset(
+        grpc_core::New<grpc::testing::FakeNonResponsiveDNSServer>(
+            g_fake_non_responsive_dns_server_port));
+    grpc_ares_test_only_inject_config = InjectBrokenNameServerList;
+    GPR_ASSERT(
+        gpr_asprintf(&whole_uri, "dns:///%s", FLAGS_target_name.c_str()));
+  } else if (FLAGS_inject_broken_nameserver_list == "False") {
+    gpr_log(GPR_INFO, "Specifying authority in uris to: %s",
+            FLAGS_local_dns_server_address.c_str());
+    GPR_ASSERT(gpr_asprintf(&whole_uri, "dns://%s/%s",
+                            FLAGS_local_dns_server_address.c_str(),
+                            FLAGS_target_name.c_str()));
+  } else {
+    gpr_log(GPR_DEBUG, "Invalid value for --inject_broken_nameserver_list.");
+    abort();
+  }
   gpr_log(GPR_DEBUG, "resolver_component_test: --enable_srv_queries: %s",
           FLAGS_enable_srv_queries.c_str());
   grpc_channel_args* resolver_args = nullptr;
@@ -524,7 +618,9 @@
                                                   CreateResultHandler(&args));
   grpc_channel_args_destroy(resolver_args);
   gpr_free(whole_uri);
-  resolver->StartLocked();
+  GRPC_CLOSURE_SCHED(GRPC_CLOSURE_CREATE(StartResolvingLocked, resolver.get(),
+                                         grpc_combiner_scheduler(args.lock)),
+                     GRPC_ERROR_NONE);
   grpc_core::ExecCtx::Get()->Flush();
   PollPollsetUntilRequestDone(&args);
   ArgsFinish(&args);
@@ -554,15 +650,11 @@
   grpc_init();
   grpc::testing::TestEnvironment env(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
-  ParseCommandLineFlags(&argc, &argv, true);
+  grpc::testing::InitTest(&argc, &argv, true);
   if (FLAGS_target_name == "") {
     gpr_log(GPR_ERROR, "Missing target_name param.");
     abort();
   }
-  if (FLAGS_local_dns_server_address != "") {
-    gpr_log(GPR_INFO, "Specifying authority in uris to: %s",
-            FLAGS_local_dns_server_address.c_str());
-  }
   auto result = RUN_ALL_TESTS();
   grpc_shutdown();
   return result;
diff --git a/test/cpp/naming/resolver_component_tests_runner.py b/test/cpp/naming/resolver_component_tests_runner.py
index a0eda79..e767e4c 100755
--- a/test/cpp/naming/resolver_component_tests_runner.py
+++ b/test/cpp/naming/resolver_component_tests_runner.py
@@ -124,9 +124,11 @@
   '--target_name', 'no-srv-ipv4-single-target.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '5.5.5.5:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -138,9 +140,11 @@
   '--target_name', 'srv-ipv4-single-target.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:1234,True',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -152,9 +156,11 @@
   '--target_name', 'srv-ipv4-multi-target.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.5:1234,True;1.2.3.6:1234,True;1.2.3.7:1234,True',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -166,9 +172,11 @@
   '--target_name', 'srv-ipv6-single-target.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '[2607:f8b0:400a:801::1001]:1234,True',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -180,9 +188,11 @@
   '--target_name', 'srv-ipv6-multi-target.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '[2607:f8b0:400a:801::1002]:1234,True;[2607:f8b0:400a:801::1003]:1234,True;[2607:f8b0:400a:801::1004]:1234,True',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -193,10 +203,12 @@
   args.test_bin_path,
   '--target_name', 'srv-ipv4-simple-service-config.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:1234,True',
-  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true}]}',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', 'round_robin',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -207,10 +219,12 @@
   args.test_bin_path,
   '--target_name', 'ipv4-no-srv-simple-service-config.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
-  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService","waitForReady":true}]}]}',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService"}],"waitForReady":true}]}',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', 'round_robin',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -222,9 +236,11 @@
   '--target_name', 'ipv4-no-config-for-cpp.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -236,9 +252,11 @@
   '--target_name', 'ipv4-cpp-config-has-zero-percentage.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -249,10 +267,12 @@
   args.test_bin_path,
   '--target_name', 'ipv4-second-language-is-cpp.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
-  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', 'round_robin',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -263,10 +283,12 @@
   args.test_bin_path,
   '--target_name', 'ipv4-config-with-percentages.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
-  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService","waitForReady":true}]}]}',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService"}],"waitForReady":true}]}',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', 'round_robin',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -278,9 +300,11 @@
   '--target_name', 'srv-ipv4-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:1234,True;1.2.3.4:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -292,9 +316,11 @@
   '--target_name', 'srv-ipv6-target-has-backend-and-balancer.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '[2607:f8b0:400a:801::1002]:1234,True;[2607:f8b0:400a:801::1002]:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -305,10 +331,12 @@
   args.test_bin_path,
   '--target_name', 'ipv4-config-causing-fallback-to-tcp.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
-  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwo","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooThree","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooFour","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooFive","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooSix","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooSeven","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooEight","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooNine","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTen","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooEleven","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true}]}',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -320,9 +348,11 @@
   '--target_name', 'srv-ipv4-single-target-srv-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '2.3.4.5:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'False',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -334,9 +364,11 @@
   '--target_name', 'srv-ipv4-multi-target-srv-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '9.2.3.5:443,False;9.2.3.6:443,False;9.2.3.7:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'False',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -348,9 +380,11 @@
   '--target_name', 'srv-ipv6-single-target-srv-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '[2600::1001]:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'False',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -362,9 +396,11 @@
   '--target_name', 'srv-ipv6-multi-target-srv-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '[2600::1002]:443,False;[2600::1003]:443,False;[2600::1004]:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'False',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -375,10 +411,12 @@
   args.test_bin_path,
   '--target_name', 'srv-ipv4-simple-service-config-srv-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '5.5.3.4:443,False',
-  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true}]}',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', 'round_robin',
   '--enable_srv_queries', 'False',
   '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -390,9 +428,11 @@
   '--target_name', 'srv-ipv4-simple-service-config-txt-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:1234,True',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'False',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -404,9 +444,11 @@
   '--target_name', 'ipv4-cpp-config-has-zero-percentage-txt-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'False',
+  '--inject_broken_nameserver_list', 'False',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
@@ -418,9 +460,107 @@
   '--target_name', 'ipv4-second-language-is-cpp-txt-disabled.resolver-tests-version-4.grpctestingexp.',
   '--expected_addrs', '1.2.3.4:443,False',
   '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
   '--expected_lb_policy', '',
   '--enable_srv_queries', 'True',
   '--enable_txt_queries', 'False',
+  '--inject_broken_nameserver_list', 'False',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-svc_cfg_bad_json.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-svc_cfg_bad_json.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_service_config_error', 'could not parse',
+  '--expected_lb_policy', '',
+  '--enable_srv_queries', 'True',
+  '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-svc_cfg_bad_client_language.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-svc_cfg_bad_client_language.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_service_config_error', 'field:clientLanguage error:should be of type array',
+  '--expected_lb_policy', '',
+  '--enable_srv_queries', 'True',
+  '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-svc_cfg_bad_percentage.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-svc_cfg_bad_percentage.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_service_config_error', 'field:percentage error:should be of type number',
+  '--expected_lb_policy', '',
+  '--enable_srv_queries', 'True',
+  '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-svc_cfg_bad_wait_for_ready.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-svc_cfg_bad_wait_for_ready.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_service_config_error', 'field:waitForReady error:Type should be true/false',
+  '--expected_lb_policy', '',
+  '--enable_srv_queries', 'True',
+  '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'False',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'no-srv-ipv4-single-target-inject-broken-nameservers.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'no-srv-ipv4-single-target-inject-broken-nameservers.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '5.5.5.5:443,False',
+  '--expected_chosen_service_config', '',
+  '--expected_service_config_error', '',
+  '--expected_lb_policy', '',
+  '--enable_srv_queries', 'True',
+  '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'True',
+  '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
+current_test_subprocess.communicate()
+if current_test_subprocess.returncode != 0:
+  num_test_failures += 1
+
+test_runner_log('Run test with target: %s' % 'ipv4-config-causing-fallback-to-tcp-inject-broken-nameservers.resolver-tests-version-4.grpctestingexp.')
+current_test_subprocess = subprocess.Popen([
+  args.test_bin_path,
+  '--target_name', 'ipv4-config-causing-fallback-to-tcp-inject-broken-nameservers.resolver-tests-version-4.grpctestingexp.',
+  '--expected_addrs', '1.2.3.4:443,False',
+  '--expected_chosen_service_config', '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}',
+  '--expected_service_config_error', 'Service config parsing error',
+  '--expected_lb_policy', '',
+  '--enable_srv_queries', 'True',
+  '--enable_txt_queries', 'True',
+  '--inject_broken_nameserver_list', 'True',
   '--local_dns_server_address', '127.0.0.1:%d' % args.dns_server_port])
 current_test_subprocess.communicate()
 if current_test_subprocess.returncode != 0:
diff --git a/test/cpp/naming/resolver_component_tests_runner_invoker.cc b/test/cpp/naming/resolver_component_tests_runner_invoker.cc
index 68be00a..d8f7100 100644
--- a/test/cpp/naming/resolver_component_tests_runner_invoker.cc
+++ b/test/cpp/naming/resolver_component_tests_runner_invoker.cc
@@ -29,6 +29,10 @@
 #include <thread>
 #include <vector>
 
+#ifdef __FreeBSD__
+#include <sys/wait.h>
+#endif
+
 #include "test/cpp/util/subprocess.h"
 #include "test/cpp/util/test_config.h"
 
diff --git a/test/cpp/naming/resolver_test_record_groups.yaml b/test/cpp/naming/resolver_test_record_groups.yaml
index 738fe65..f72a7ea 100644
--- a/test/cpp/naming/resolver_test_record_groups.yaml
+++ b/test/cpp/naming/resolver_test_record_groups.yaml
@@ -4,9 +4,11 @@
 - expected_addrs:
   - {address: '5.5.5.5:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: no-srv-ipv4-single-target
   records:
     no-srv-ipv4-single-target:
@@ -14,9 +16,11 @@
 - expected_addrs:
   - {address: '1.2.3.4:1234', is_balancer: true}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-single-target
   records:
     _grpclb._tcp.srv-ipv4-single-target:
@@ -28,9 +32,11 @@
   - {address: '1.2.3.6:1234', is_balancer: true}
   - {address: '1.2.3.7:1234', is_balancer: true}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-multi-target
   records:
     _grpclb._tcp.srv-ipv4-multi-target:
@@ -42,9 +48,11 @@
 - expected_addrs:
   - {address: '[2607:f8b0:400a:801::1001]:1234', is_balancer: true}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv6-single-target
   records:
     _grpclb._tcp.srv-ipv6-single-target:
@@ -56,9 +64,11 @@
   - {address: '[2607:f8b0:400a:801::1003]:1234', is_balancer: true}
   - {address: '[2607:f8b0:400a:801::1004]:1234', is_balancer: true}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv6-multi-target
   records:
     _grpclb._tcp.srv-ipv6-multi-target:
@@ -69,10 +79,12 @@
     - {TTL: '2100', data: '2607:f8b0:400a:801::1004', type: AAAA}
 - expected_addrs:
   - {address: '1.2.3.4:1234', is_balancer: true}
-  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}'
+  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true}]}'
+  expected_service_config_error: null
   expected_lb_policy: round_robin
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-simple-service-config
   records:
     _grpclb._tcp.srv-ipv4-simple-service-config:
@@ -80,80 +92,92 @@
     ipv4-simple-service-config:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.srv-ipv4-simple-service-config:
-    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
-  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService","waitForReady":true}]}]}'
+  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService"}],"waitForReady":true}]}'
+  expected_service_config_error: null
   expected_lb_policy: round_robin
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-no-srv-simple-service-config
   records:
     ipv4-no-srv-simple-service-config:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-no-srv-simple-service-config:
-    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NoSrvSimpleService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-no-config-for-cpp
   records:
     ipv4-no-config-for-cpp:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-no-config-for-cpp:
-    - {TTL: '2100', data: 'grpc_config=[{"clientLanguage":["python"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"PythonService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"clientLanguage":["python"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"PythonService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-cpp-config-has-zero-percentage
   records:
     ipv4-cpp-config-has-zero-percentage:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-cpp-config-has-zero-percentage:
-    - {TTL: '2100', data: 'grpc_config=[{"percentage":0,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"percentage":0,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
-  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}'
+  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}'
+  expected_service_config_error: null
   expected_lb_policy: round_robin
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-second-language-is-cpp
   records:
     ipv4-second-language-is-cpp:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-second-language-is-cpp:
-    - {TTL: '2100', data: 'grpc_config=[{"clientLanguage":["go"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"GoService","waitForReady":true}]}]}},{"clientLanguage":["c++"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"clientLanguage":["go"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"GoService"}],"waitForReady":true}]}},{"clientLanguage":["c++"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
-  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService","waitForReady":true}]}]}'
+  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService"}],"waitForReady":true}]}'
+  expected_service_config_error: null
   expected_lb_policy: round_robin
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-config-with-percentages
   records:
     ipv4-config-with-percentages:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-config-with-percentages:
-    - {TTL: '2100', data: 'grpc_config=[{"percentage":0,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NeverPickedService","waitForReady":true}]}]}},{"percentage":100,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"percentage":0,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"NeverPickedService"}],"waitForReady":true}]}},{"percentage":100,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"AlwaysPickedService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:1234', is_balancer: true}
   - {address: '1.2.3.4:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-target-has-backend-and-balancer
   records:
     _grpclb._tcp.srv-ipv4-target-has-backend-and-balancer:
@@ -166,9 +190,11 @@
   - {address: '[2607:f8b0:400a:801::1002]:1234', is_balancer: true}
   - {address: '[2607:f8b0:400a:801::1002]:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv6-target-has-backend-and-balancer
   records:
     _grpclb._tcp.srv-ipv6-target-has-backend-and-balancer:
@@ -179,24 +205,28 @@
     - {TTL: '2100', data: '2607:f8b0:400a:801::1002', type: AAAA}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
-  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}'
+  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwo","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooThree","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooFour","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooFive","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooSix","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooSeven","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooEight","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooNine","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTen","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooEleven","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true}]}'
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-config-causing-fallback-to-tcp
   records:
     ipv4-config-causing-fallback-to-tcp:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-config-causing-fallback-to-tcp:
-    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwo","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooThree","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooFour","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooFive","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooSix","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooSeven","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooEight","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooNine","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTen","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooEleven","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true},{"name":[{"method":"FooTwelve","service":"SimpleService"}],"waitForReady":true}]}}]',
       type: TXT}
 # Tests for which we don't enable SRV queries
 - expected_addrs:
   - {address: '2.3.4.5:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: false
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-single-target-srv-disabled
   records:
     _grpclb._tcp.srv-ipv4-single-target-srv-disabled:
@@ -210,9 +240,11 @@
   - {address: '9.2.3.6:443', is_balancer: false}
   - {address: '9.2.3.7:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: false
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-multi-target-srv-disabled
   records:
     _grpclb._tcp.srv-ipv4-multi-target-srv-disabled:
@@ -228,9 +260,11 @@
 - expected_addrs:
   - {address: '[2600::1001]:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: false
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv6-single-target-srv-disabled
   records:
     _grpclb._tcp.srv-ipv6-single-target-srv-disabled:
@@ -244,9 +278,11 @@
   - {address: '[2600::1003]:443', is_balancer: false}
   - {address: '[2600::1004]:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: false
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv6-multi-target-srv-disabled
   records:
     _grpclb._tcp.srv-ipv6-multi-target-srv-disabled:
@@ -261,10 +297,12 @@
     - {TTL: '2100', data: '2600::1004', type: AAAA}
 - expected_addrs:
   - {address: '5.5.3.4:443', is_balancer: false}
-  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}'
+  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true}]}'
+  expected_service_config_error: null
   expected_lb_policy: round_robin
   enable_srv_queries: false
   enable_txt_queries: true
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-simple-service-config-srv-disabled
   records:
     _grpclb._tcp.srv-ipv4-simple-service-config-srv-disabled:
@@ -274,14 +312,16 @@
     srv-ipv4-simple-service-config-srv-disabled:
     - {TTL: '2100', data: 5.5.3.4, type: A}
     _grpc_config.srv-ipv4-simple-service-config-srv-disabled:
-    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:1234', is_balancer: true}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: false
+  inject_broken_nameserver_list: false
   record_to_resolve: srv-ipv4-simple-service-config-txt-disabled
   records:
     _grpclb._tcp.srv-ipv4-simple-service-config-txt-disabled:
@@ -289,31 +329,123 @@
     ipv4-simple-service-config-txt-disabled:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.srv-ipv4-simple-service-config-txt-disabled:
-    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: false
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-cpp-config-has-zero-percentage-txt-disabled
   records:
     ipv4-cpp-config-has-zero-percentage-txt-disabled:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-cpp-config-has-zero-percentage-txt-disabled:
-    - {TTL: '2100', data: 'grpc_config=[{"percentage":0,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"percentage":0,"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}}]',
       type: TXT}
 - expected_addrs:
   - {address: '1.2.3.4:443', is_balancer: false}
   expected_chosen_service_config: null
+  expected_service_config_error: null
   expected_lb_policy: null
   enable_srv_queries: true
   enable_txt_queries: false
+  inject_broken_nameserver_list: false
   record_to_resolve: ipv4-second-language-is-cpp-txt-disabled
   records:
     ipv4-second-language-is-cpp-txt-disabled:
     - {TTL: '2100', data: 1.2.3.4, type: A}
     _grpc_config.ipv4-second-language-is-cpp-txt-disabled:
-    - {TTL: '2100', data: 'grpc_config=[{"clientLanguage":["go"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"GoService","waitForReady":true}]}]}},{"clientLanguage":["c++"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService","waitForReady":true}]}]}}]',
+    - {TTL: '2100', data: 'grpc_config=[{"clientLanguage":["go"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"GoService"}],"waitForReady":true}]}},{"clientLanguage":["c++"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}}]',
+      type: TXT}
+- expected_addrs:
+  - {address: '1.2.3.4:443', is_balancer: false}
+  expected_chosen_service_config: null
+  expected_service_config_error: 'could not parse'
+  expected_lb_policy: null
+  enable_srv_queries: true
+  enable_txt_queries: true
+  inject_broken_nameserver_list: false
+  record_to_resolve: ipv4-svc_cfg_bad_json
+  records:
+    ipv4-svc_cfg_bad_json:
+    - {TTL: '2100', data: 1.2.3.4, type: A}
+    _grpc_config.ipv4-svc_cfg_bad_json:
+    - {TTL: '2100', data: 'grpc_config=[{]',
+      type: TXT}
+- expected_addrs:
+  - {address: '1.2.3.4:443', is_balancer: false}
+  expected_chosen_service_config: null
+  expected_service_config_error: 'field:clientLanguage error:should be of type array'
+  expected_lb_policy: null
+  enable_srv_queries: true
+  enable_txt_queries: true
+  inject_broken_nameserver_list: false
+  record_to_resolve: ipv4-svc_cfg_bad_client_language
+  records:
+    ipv4-svc_cfg_bad_client_language:
+    - {TTL: '2100', data: 1.2.3.4, type: A}
+    _grpc_config.ipv4-svc_cfg_bad_client_language:
+    - {TTL: '2100', data: 'grpc_config=[{"clientLanguage":"go","serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"GoService"}],"waitForReady":true}]}},{"clientLanguage":["c++"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}}]',
+      type: TXT}
+- expected_addrs:
+  - {address: '1.2.3.4:443', is_balancer: false}
+  expected_chosen_service_config: null
+  expected_service_config_error: 'field:percentage error:should be of type number'
+  expected_lb_policy: null
+  enable_srv_queries: true
+  enable_txt_queries: true
+  inject_broken_nameserver_list: false
+  record_to_resolve: ipv4-svc_cfg_bad_percentage
+  records:
+    ipv4-svc_cfg_bad_percentage:
+    - {TTL: '2100', data: 1.2.3.4, type: A}
+    _grpc_config.ipv4-svc_cfg_bad_percentage:
+    - {TTL: '2100', data: 'grpc_config=[{"percentage":"0","serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"GoService"}],"waitForReady":true}]}},{"clientLanguage":["c++"],"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":true}]}}]',
+      type: TXT}
+- expected_addrs:
+  - {address: '1.2.3.4:443', is_balancer: false}
+  expected_chosen_service_config: null
+  expected_service_config_error: 'field:waitForReady error:Type should be true/false'
+  expected_lb_policy: null
+  enable_srv_queries: true
+  enable_txt_queries: true
+  inject_broken_nameserver_list: false
+  record_to_resolve: ipv4-svc_cfg_bad_wait_for_ready
+  records:
+    ipv4-svc_cfg_bad_wait_for_ready:
+    - {TTL: '2100', data: 1.2.3.4, type: A}
+    _grpc_config.ipv4-svc_cfg_bad_wait_for_ready:
+    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"methodConfig":[{"name":[{"method":"Foo","service":"CppService"}],"waitForReady":"true"}]}}]',
+      type: TXT}      
+# Tests for which we also exercise the resolver's ability to skip past a broken DNS server in its nameserver list
+- expected_addrs:
+  - {address: '5.5.5.5:443', is_balancer: false}
+  expected_chosen_service_config: null
+  expected_service_config_error: null
+  expected_lb_policy: null
+  enable_srv_queries: true
+  enable_txt_queries: true
+  inject_broken_nameserver_list: true
+  record_to_resolve: no-srv-ipv4-single-target-inject-broken-nameservers
+  records:
+    no-srv-ipv4-single-target-inject-broken-nameservers:
+    - {TTL: '2100', data: 5.5.5.5, type: A}
+- expected_addrs:
+  - {address: '1.2.3.4:443', is_balancer: false}
+  expected_chosen_service_config: '{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}'
+  expected_service_config_error: 'Service config parsing error'
+  expected_lb_policy: null
+  enable_srv_queries: true
+  enable_txt_queries: true
+  inject_broken_nameserver_list: true
+  record_to_resolve: ipv4-config-causing-fallback-to-tcp-inject-broken-nameservers
+  records:
+    ipv4-config-causing-fallback-to-tcp-inject-broken-nameservers:
+    - {TTL: '2100', data: 1.2.3.4, type: A}
+    _grpc_config.ipv4-config-causing-fallback-to-tcp-inject-broken-nameservers:
+    - {TTL: '2100', data: 'grpc_config=[{"serviceConfig":{"loadBalancingPolicy":"round_robin","methodConfig":[{"name":[{"method":"Foo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwo","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooThree","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFour","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooFive","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSix","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooSeven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEight","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooNine","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTen","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooEleven","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]},{"name":[{"method":"FooTwelve","service":"SimpleService","waitForReady":true}]}]}}]',
       type: TXT}
diff --git a/test/cpp/performance/writes_per_rpc_test.cc b/test/cpp/performance/writes_per_rpc_test.cc
index 7b22f23..b74f4d0 100644
--- a/test/cpp/performance/writes_per_rpc_test.cc
+++ b/test/cpp/performance/writes_per_rpc_test.cc
@@ -118,7 +118,7 @@
           "target", &c_args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
       grpc_chttp2_transport_start_reading(transport, nullptr, nullptr);
 
-      channel_ = CreateChannelInternal(
+      channel_ = ::grpc::CreateChannelInternal(
           "", channel,
           std::vector<std::unique_ptr<
               experimental::ClientInterceptorFactoryInterface>>());
diff --git a/test/cpp/qps/client_callback.cc b/test/cpp/qps/client_callback.cc
index 815780e..dcfa2db 100644
--- a/test/cpp/qps/client_callback.cc
+++ b/test/cpp/qps/client_callback.cc
@@ -285,8 +285,18 @@
       }
       return;
     }
-    write_time_ = UsageTimer::Now();
-    StartWrite(client_->request());
+    if (!client_->IsClosedLoop()) {
+      gpr_timespec next_issue_time = client_->NextRPCIssueTime();
+      // Start an alarm callback to run the internal callback after
+      // next_issue_time
+      ctx_->alarm_.experimental().Set(next_issue_time, [this](bool ok) {
+        write_time_ = UsageTimer::Now();
+        StartWrite(client_->request());
+      });
+    } else {
+      write_time_ = UsageTimer::Now();
+      StartWrite(client_->request());
+    }
   }
 
   void OnDone(const Status& s) override {
diff --git a/test/cpp/qps/driver.cc b/test/cpp/qps/driver.cc
index 181e11f..7d4d5d9 100644
--- a/test/cpp/qps/driver.cc
+++ b/test/cpp/qps/driver.cc
@@ -288,7 +288,7 @@
     gpr_log(GPR_INFO, "Starting server on %s (worker #%" PRIuPTR ")",
             workers[i].c_str(), i);
     if (!run_inproc) {
-      servers[i].stub = WorkerService::NewStub(CreateChannel(
+      servers[i].stub = WorkerService::NewStub(grpc::CreateChannel(
           workers[i], GetCredentialsProvider()->GetChannelCredentials(
                           GetCredType(workers[i], per_worker_credential_types,
                                       credential_type),
@@ -349,7 +349,7 @@
     gpr_log(GPR_INFO, "Starting client on %s (worker #%" PRIuPTR ")",
             worker.c_str(), i + num_servers);
     if (!run_inproc) {
-      clients[i].stub = WorkerService::NewStub(CreateChannel(
+      clients[i].stub = WorkerService::NewStub(grpc::CreateChannel(
           worker,
           GetCredentialsProvider()->GetChannelCredentials(
               GetCredType(worker, per_worker_credential_types, credential_type),
@@ -557,7 +557,7 @@
 
   ChannelArguments channel_args;
   for (size_t i = 0; i < workers.size(); i++) {
-    auto stub = WorkerService::NewStub(CreateChannel(
+    auto stub = WorkerService::NewStub(grpc::CreateChannel(
         workers[i], GetCredentialsProvider()->GetChannelCredentials(
                         GetCredType(workers[i], per_worker_credential_types,
                                     credential_type),
diff --git a/test/cpp/qps/json_run_localhost.cc b/test/cpp/qps/json_run_localhost.cc
index 948c308..42cc9e0 100644
--- a/test/cpp/qps/json_run_localhost.cc
+++ b/test/cpp/qps/json_run_localhost.cc
@@ -24,6 +24,10 @@
 #include <sstream>
 #include <string>
 
+#ifdef __FreeBSD__
+#include <sys/wait.h>
+#endif
+
 #include <grpc/support/log.h>
 
 #include "src/core/lib/gpr/env.h"
diff --git a/test/cpp/server/server_request_call_test.cc b/test/cpp/server/server_request_call_test.cc
index 9831c06..14b735c 100644
--- a/test/cpp/server/server_request_call_test.cc
+++ b/test/cpp/server/server_request_call_test.cc
@@ -115,7 +115,7 @@
   });
 
   auto stub = testing::EchoTestService::NewStub(
-      CreateChannel(address, InsecureChannelCredentials()));
+      grpc::CreateChannel(address, InsecureChannelCredentials()));
 
   for (int i = 0; i < 100; i++) {
     gpr_log(GPR_INFO, "Sending %d.", i);
diff --git a/test/cpp/util/cli_call_test.cc b/test/cpp/util/cli_call_test.cc
index 1d64153..a91705b 100644
--- a/test/cpp/util/cli_call_test.cc
+++ b/test/cpp/util/cli_call_test.cc
@@ -74,8 +74,8 @@
   void TearDown() override { server_->Shutdown(); }
 
   void ResetStub() {
-    channel_ =
-        CreateChannel(server_address_.str(), InsecureChannelCredentials());
+    channel_ = grpc::CreateChannel(server_address_.str(),
+                                   InsecureChannelCredentials());
     stub_ = grpc::testing::EchoTestService::NewStub(channel_);
   }
 
diff --git a/test/cpp/util/create_test_channel.cc b/test/cpp/util/create_test_channel.cc
index 79a5e13..5c32198 100644
--- a/test/cpp/util/create_test_channel.cc
+++ b/test/cpp/util/create_test_channel.cc
@@ -34,7 +34,7 @@
  public:
   std::shared_ptr<ChannelCredentials> GetChannelCredentials(
       grpc::ChannelArguments* args) override {
-    return SslCredentials(SslCredentialsOptions());
+    return grpc::SslCredentials(SslCredentialsOptions());
   }
   std::shared_ptr<ServerCredentials> GetServerCredentials() override {
     return nullptr;
@@ -116,9 +116,9 @@
                                                                &channel_args);
   GPR_ASSERT(channel_creds != nullptr);
   if (creds.get()) {
-    channel_creds = CompositeChannelCredentials(channel_creds, creds);
+    channel_creds = grpc::CompositeChannelCredentials(channel_creds, creds);
   }
-  return CreateCustomChannel(server, channel_creds, channel_args);
+  return ::grpc::CreateCustomChannel(server, channel_creds, channel_args);
 }
 
 std::shared_ptr<Channel> CreateTestChannel(
@@ -132,7 +132,8 @@
   std::shared_ptr<ChannelCredentials> channel_creds;
   if (cred_type.empty()) {
     if (interceptor_creators.empty()) {
-      return CreateCustomChannel(server, InsecureChannelCredentials(), args);
+      return ::grpc::CreateCustomChannel(server, InsecureChannelCredentials(),
+                                         args);
     } else {
       return experimental::CreateCustomChannelWithInterceptors(
           server, InsecureChannelCredentials(), args,
@@ -156,10 +157,11 @@
     const grpc::string& connect_to =
         server.empty() ? override_hostname : server;
     if (creds.get()) {
-      channel_creds = CompositeChannelCredentials(channel_creds, creds);
+      channel_creds = grpc::CompositeChannelCredentials(channel_creds, creds);
     }
     if (interceptor_creators.empty()) {
-      return CreateCustomChannel(connect_to, channel_creds, channel_args);
+      return ::grpc::CreateCustomChannel(connect_to, channel_creds,
+                                         channel_args);
     } else {
       return experimental::CreateCustomChannelWithInterceptors(
           connect_to, channel_creds, channel_args,
@@ -171,7 +173,7 @@
     GPR_ASSERT(channel_creds != nullptr);
 
     if (interceptor_creators.empty()) {
-      return CreateCustomChannel(server, channel_creds, args);
+      return ::grpc::CreateCustomChannel(server, channel_creds, args);
     } else {
       return experimental::CreateCustomChannelWithInterceptors(
           server, channel_creds, args, std::move(interceptor_creators));
@@ -220,7 +222,7 @@
                                                                &channel_args);
   GPR_ASSERT(channel_creds != nullptr);
   if (creds.get()) {
-    channel_creds = CompositeChannelCredentials(channel_creds, creds);
+    channel_creds = grpc::CompositeChannelCredentials(channel_creds, creds);
   }
   return experimental::CreateCustomChannelWithInterceptors(
       server, channel_creds, channel_args, std::move(interceptor_creators));
diff --git a/test/cpp/util/create_test_channel.h b/test/cpp/util/create_test_channel.h
index b50131b..42564a3 100644
--- a/test/cpp/util/create_test_channel.h
+++ b/test/cpp/util/create_test_channel.h
@@ -24,8 +24,12 @@
 #include <grpcpp/impl/codegen/client_interceptor.h>
 #include <grpcpp/security/credentials.h>
 
-namespace grpc {
+namespace grpc_impl {
+
 class Channel;
+}
+
+namespace grpc {
 
 namespace testing {
 
@@ -33,31 +37,31 @@
 
 }  // namespace testing
 
-std::shared_ptr<Channel> CreateTestChannel(
+std::shared_ptr<::grpc_impl::Channel> CreateTestChannel(
     const grpc::string& server, testing::transport_security security_type);
 
-std::shared_ptr<Channel> CreateTestChannel(
+std::shared_ptr<::grpc_impl::Channel> CreateTestChannel(
     const grpc::string& server, const grpc::string& override_hostname,
     testing::transport_security security_type, bool use_prod_roots);
 
-std::shared_ptr<Channel> CreateTestChannel(
+std::shared_ptr<::grpc_impl::Channel> CreateTestChannel(
     const grpc::string& server, const grpc::string& override_hostname,
     testing::transport_security security_type, bool use_prod_roots,
     const std::shared_ptr<CallCredentials>& creds);
 
-std::shared_ptr<Channel> CreateTestChannel(
+std::shared_ptr<::grpc_impl::Channel> CreateTestChannel(
     const grpc::string& server, const grpc::string& override_hostname,
     testing::transport_security security_type, bool use_prod_roots,
     const std::shared_ptr<CallCredentials>& creds,
     const ChannelArguments& args);
 
-std::shared_ptr<Channel> CreateTestChannel(
+std::shared_ptr<::grpc_impl::Channel> CreateTestChannel(
     const grpc::string& server, const grpc::string& cred_type,
     const grpc::string& override_hostname, bool use_prod_roots,
     const std::shared_ptr<CallCredentials>& creds,
     const ChannelArguments& args);
 
-std::shared_ptr<Channel> CreateTestChannel(
+std::shared_ptr<::grpc_impl::Channel> CreateTestChannel(
     const grpc::string& server, const grpc::string& credential_type,
     const std::shared_ptr<CallCredentials>& creds);
 
diff --git a/test/cpp/util/grpc_tool.cc b/test/cpp/util/grpc_tool.cc
index 44b14bf..dbac311 100644
--- a/test/cpp/util/grpc_tool.cc
+++ b/test/cpp/util/grpc_tool.cc
@@ -217,7 +217,8 @@
   if (!cred.GetSslTargetNameOverride().empty()) {
     args.SetSslTargetNameOverride(cred.GetSslTargetNameOverride());
   }
-  return grpc::CreateCustomChannel(server_address, cred.GetCredentials(), args);
+  return ::grpc::CreateCustomChannel(server_address, cred.GetCredentials(),
+                                     args);
 }
 
 struct Command {
diff --git a/test/cpp/util/grpc_tool_test.cc b/test/cpp/util/grpc_tool_test.cc
index 57cdbeb..e44ada4 100644
--- a/test/cpp/util/grpc_tool_test.cc
+++ b/test/cpp/util/grpc_tool_test.cc
@@ -122,7 +122,7 @@
       return InsecureChannelCredentials();
     }
     SslCredentialsOptions ssl_opts = {test_root_cert, "", ""};
-    return SslCredentials(grpc::SslCredentialsOptions(ssl_opts));
+    return grpc::SslCredentials(grpc::SslCredentialsOptions(ssl_opts));
   }
   const grpc::string GetCredentialUsage() const override { return ""; }
 
diff --git a/test/cpp/util/metrics_server.h b/test/cpp/util/metrics_server.h
index 2d6ddf0..08f6034 100644
--- a/test/cpp/util/metrics_server.h
+++ b/test/cpp/util/metrics_server.h
@@ -21,6 +21,8 @@
 #include <map>
 #include <mutex>
 
+#include <grpcpp/server.h>
+
 #include "src/proto/grpc/testing/metrics.grpc.pb.h"
 #include "src/proto/grpc/testing/metrics.pb.h"
 
diff --git a/test/cpp/util/test_credentials_provider.cc b/test/cpp/util/test_credentials_provider.cc
index 0cf75f1..455f94e 100644
--- a/test/cpp/util/test_credentials_provider.cc
+++ b/test/cpp/util/test_credentials_provider.cc
@@ -24,6 +24,7 @@
 
 #include <grpc/support/log.h>
 #include <grpc/support/sync.h>
+#include <grpcpp/security/server_credentials.h>
 
 #include "test/core/end2end/data/ssl_test_data.h"
 
@@ -62,7 +63,7 @@
     } else if (type == grpc::testing::kTlsCredentialsType) {
       SslCredentialsOptions ssl_opts = {test_root_cert, "", ""};
       args->SetSslTargetNameOverride("foo.test.google.fr");
-      return SslCredentials(ssl_opts);
+      return grpc::SslCredentials(ssl_opts);
     } else if (type == grpc::testing::kGoogleDefaultCredentialsType) {
       return grpc::GoogleDefaultCredentials();
     } else {
diff --git a/third_party/cares/cares.BUILD b/third_party/cares/cares.BUILD
index 66ec174..78a4590 100644
--- a/third_party/cares/cares.BUILD
+++ b/third_party/cares/cares.BUILD
@@ -144,6 +144,7 @@
         "ares_strdup.h",
         "ares_strsplit.h",
         "ares_version.h",
+        "ares_writev.h",
         "bitncmp.h",
         "config-win32.h",
         "nameser.h",
diff --git a/third_party/toolchains/BUILD b/third_party/toolchains/BUILD
index 943690a..0ff68f9 100644
--- a/third_party/toolchains/BUILD
+++ b/third_party/toolchains/BUILD
@@ -16,38 +16,49 @@
 
 package(default_visibility = ["//visibility:public"])
 
-# Latest RBE Ubuntu16_04 container
-# Update every time when a new container is released.
 alias(
-    name = "rbe_ubuntu1604",
-    actual = ":rbe_ubuntu1604_r346485",
+    name = "rbe_windows",
+    actual = ":rbe_windows_1803",
 )
 
-alias(
-    name = "rbe_ubuntu1604_large",
-    actual = ":rbe_ubuntu1604_r346485_large",
-)
-
-# RBE Ubuntu16_04 r346485
+# RBE Windows
 platform(
-    name = "rbe_ubuntu1604_r346485",
+    name = "rbe_windows_1803",
     constraint_values = [
         "@bazel_tools//platforms:x86_64",
-        "@bazel_tools//platforms:linux",
-        "@bazel_tools//tools/cpp:clang",
-        "@com_github_bazelbuild_bazeltoolchains//constraints:xenial",
-        "@com_github_bazelbuild_bazeltoolchains//constraints/sanitizers:support_msan",
-        "//third_party/toolchains/machine_size:standard",
+        "@bazel_tools//platforms:windows",
+        "@bazel_tools//tools/cpp:msvc",
     ],
     remote_execution_properties = """
         properties: {
           name: "container-image"
-          value:"docker://gcr.io/cloud-marketplace/google/rbe-ubuntu16-04@sha256:f3120a030a19d67626ababdac79cc787e699a1aa924081431285118f87e7b375"
+          value:"docker://gcr.io/grpc-testing/rbe_windows_toolchain@sha256:689b177e4a157c431c7077d19d043de27922c37de835031f29c9093b8d5c6370"
         }
         properties: {
           name: "gceMachineType"  # Small machines for majority of tests.
           value: "n1-highmem-2"
         }
+        properties:{
+            name: "OSFamily"
+            value: "Windows"
+        }
+
+        """,
+)
+
+# RBE Ubuntu16_04 r346485
+platform(
+    name = "rbe_ubuntu1604",
+    parents = ["@rbe_default//config:platform"],
+    constraint_values = [
+        "//third_party/toolchains/machine_size:standard",
+    ],
+    remote_execution_properties = """
+        {PARENT_REMOTE_EXECUTION_PROPERTIES}
+        properties: {
+          name: "gceMachineType"  # Small machines for majority of tests.
+          value: "n1-highmem-2"
+        }
         properties: {
             name: "dockerSiblingContainers"
             value: "false"
@@ -67,22 +78,14 @@
         """,
 )
 
-# RBE Ubuntu16_04 r346485 large
 platform(
-    name = "rbe_ubuntu1604_r346485_large",
+    name = "rbe_ubuntu1604_large",
+    parents = ["@rbe_default//config:platform"],
     constraint_values = [
-        "@bazel_tools//platforms:x86_64",
-        "@bazel_tools//platforms:linux",
-        "@bazel_tools//tools/cpp:clang",
-        "@com_github_bazelbuild_bazeltoolchains//constraints:xenial",
-        "@com_github_bazelbuild_bazeltoolchains//constraints/sanitizers:support_msan",
         "//third_party/toolchains/machine_size:large",
     ],
     remote_execution_properties = """
-        properties: {
-          name: "container-image"
-          value:"docker://gcr.io/cloud-marketplace/google/rbe-ubuntu16-04@sha256:f3120a030a19d67626ababdac79cc787e699a1aa924081431285118f87e7b375"
-        }
+        {PARENT_REMOTE_EXECUTION_PROPERTIES}
         properties: {
           name: "gceMachineType"  # Large machines for some resource demanding tests (TSAN).
           value: "n1-standard-8"
@@ -106,12 +109,18 @@
     """,
 )
 
-toolchain(
-    name = "cc-toolchain-clang-x86_64-default",
-    exec_compatible_with = [
+platform(
+    name = "local",
+    parents = ["@bazel_tools//platforms:target_platform"],
+    constraint_values = [
+        "//third_party/toolchains/machine_size:standard",
     ],
-    target_compatible_with = [
+)
+
+platform(
+    name = "local_large",
+    parents = ["@bazel_tools//platforms:target_platform"],
+    constraint_values = [
+        "//third_party/toolchains/machine_size:large",
     ],
-    toolchain = "@com_github_bazelbuild_bazeltoolchains//configs/ubuntu16_04_clang/1.1/bazel_0.20.0/default:cc-compiler-k8",
-    toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
 )
diff --git a/third_party/toolchains/bazel_0.23.2_rbe_windows/BUILD b/third_party/toolchains/bazel_0.23.2_rbe_windows/BUILD
new file mode 100644
index 0000000..5ce4e00
--- /dev/null
+++ b/third_party/toolchains/bazel_0.23.2_rbe_windows/BUILD
@@ -0,0 +1,188 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+# This becomes the BUILD file for @local_config_cc// under Windows.
+licenses(["notice"])  # Apache v2
+
+package(default_visibility = ["//visibility:public"])
+
+load(":cc_toolchain_config.bzl", "cc_toolchain_config")
+
+cc_library(
+    name = "malloc",
+)
+
+filegroup(
+    name = "empty",
+    srcs = [],
+)
+
+# Hardcoded toolchain, legacy behaviour.
+cc_toolchain_suite(
+    name = "toolchain",
+    toolchains = {
+        "armeabi-v7a|compiler": ":cc-compiler-armeabi-v7a",
+        "x64_windows|msvc-cl": ":cc-compiler-x64_windows",
+        "x64_windows|msys-gcc": ":cc-compiler-x64_windows_msys",
+        "x64_windows|mingw-gcc": ":cc-compiler-x64_windows_mingw",
+        "x64_windows_msys": ":cc-compiler-x64_windows_msys",
+        "x64_windows": ":cc-compiler-x64_windows",
+        "armeabi-v7a": ":cc-compiler-armeabi-v7a",
+    },
+)
+
+cc_toolchain(
+    name = "cc-compiler-x64_windows_msys",
+    all_files = ":empty",
+    ar_files = ":empty",
+    as_files = ":empty",
+    compiler_files = ":empty",
+    dwp_files = ":empty",
+    linker_files = ":empty",
+    objcopy_files = ":empty",
+    strip_files = ":empty",
+    supports_param_files = 1,
+    toolchain_config = ":msys_x64",
+    toolchain_identifier = "msys_x64",
+)
+
+cc_toolchain_config(
+    name = "msys_x64",
+    compiler = "msys-gcc",
+    cpu = "x64_windows",
+)
+
+toolchain(
+    name = "cc-toolchain-x64_windows_msys",
+    exec_compatible_with = [
+        "@bazel_tools//platforms:x86_64",
+        "@bazel_tools//platforms:windows",
+        "@bazel_tools//tools/cpp:msys",
+    ],
+    target_compatible_with = [
+        "@bazel_tools//platforms:x86_64",
+        "@bazel_tools//platforms:windows",
+    ],
+    toolchain = ":cc-compiler-x64_windows_msys",
+    toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
+
+cc_toolchain(
+    name = "cc-compiler-x64_windows_mingw",
+    all_files = ":empty",
+    ar_files = ":empty",
+    as_files = ":empty",
+    compiler_files = ":empty",
+    dwp_files = ":empty",
+    linker_files = ":empty",
+    objcopy_files = ":empty",
+    strip_files = ":empty",
+    supports_param_files = 0,
+    toolchain_config = ":msys_x64_mingw",
+    toolchain_identifier = "msys_x64_mingw",
+)
+
+cc_toolchain_config(
+    name = "msys_x64_mingw",
+    compiler = "mingw-gcc",
+    cpu = "x64_windows",
+)
+
+toolchain(
+    name = "cc-toolchain-x64_windows_mingw",
+    exec_compatible_with = [
+        "@bazel_tools//platforms:x86_64",
+        "@bazel_tools//platforms:windows",
+        "@bazel_tools//tools/cpp:mingw",
+    ],
+    target_compatible_with = [
+        "@bazel_tools//platforms:x86_64",
+        "@bazel_tools//platforms:windows",
+    ],
+    toolchain = ":cc-compiler-x64_windows_mingw",
+    toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
+
+cc_toolchain(
+    name = "cc-compiler-x64_windows",
+    all_files = ":empty",
+    ar_files = ":empty",
+    as_files = ":empty",
+    compiler_files = ":empty",
+    dwp_files = ":empty",
+    linker_files = ":empty",
+    objcopy_files = ":empty",
+    strip_files = ":empty",
+    supports_param_files = 1,
+    toolchain_config = ":msvc_x64",
+    toolchain_identifier = "msvc_x64",
+)
+
+cc_toolchain_config(
+    name = "msvc_x64",
+    compiler = "msvc-cl",
+    cpu = "x64_windows",
+)
+
+toolchain(
+    name = "cc-toolchain-x64_windows",
+    exec_compatible_with = [
+        "@bazel_tools//platforms:x86_64",
+        "@bazel_tools//platforms:windows",
+    ],
+    target_compatible_with = [
+        "@bazel_tools//platforms:x86_64",
+        "@bazel_tools//platforms:windows",
+    ],
+    toolchain = ":cc-compiler-x64_windows",
+    toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
+
+cc_toolchain(
+    name = "cc-compiler-armeabi-v7a",
+    all_files = ":empty",
+    ar_files = ":empty",
+    as_files = ":empty",
+    compiler_files = ":empty",
+    dwp_files = ":empty",
+    linker_files = ":empty",
+    objcopy_files = ":empty",
+    strip_files = ":empty",
+    supports_param_files = 1,
+    toolchain_config = ":stub_armeabi-v7a",
+    toolchain_identifier = "stub_armeabi-v7a",
+)
+
+cc_toolchain_config(
+    name = "stub_armeabi-v7a",
+    compiler = "compiler",
+    cpu = "armeabi-v7a",
+)
+
+toolchain(
+    name = "cc-toolchain-armeabi-v7a",
+    exec_compatible_with = [
+    ],
+    target_compatible_with = [
+        "@bazel_tools//platforms:arm",
+        "@bazel_tools//platforms:android",
+    ],
+    toolchain = ":cc-compiler-armeabi-v7a",
+    toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
+
+filegroup(
+    name = "link_dynamic_library",
+    srcs = ["link_dynamic_library.sh"],
+)
diff --git a/third_party/toolchains/bazel_0.23.2_rbe_windows/cc_toolchain_config.bzl b/third_party/toolchains/bazel_0.23.2_rbe_windows/cc_toolchain_config.bzl
new file mode 100644
index 0000000..790ddcb
--- /dev/null
+++ b/third_party/toolchains/bazel_0.23.2_rbe_windows/cc_toolchain_config.bzl
@@ -0,0 +1,1704 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""A Starlark cc_toolchain configuration rule"""
+
+load(
+    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
+    "action_config",
+    "artifact_name_pattern",
+    "env_entry",
+    "env_set",
+    "feature",
+    "feature_set",
+    "flag_group",
+    "flag_set",
+    "make_variable",
+    "tool",
+    "tool_path",
+    "variable_with_value",
+    "with_feature_set",
+)
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+
+all_compile_actions = [
+    ACTION_NAMES.c_compile,
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.assemble,
+    ACTION_NAMES.preprocess_assemble,
+    ACTION_NAMES.cpp_header_parsing,
+    ACTION_NAMES.cpp_module_compile,
+    ACTION_NAMES.cpp_module_codegen,
+    ACTION_NAMES.clif_match,
+    ACTION_NAMES.lto_backend,
+]
+
+all_cpp_compile_actions = [
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.cpp_header_parsing,
+    ACTION_NAMES.cpp_module_compile,
+    ACTION_NAMES.cpp_module_codegen,
+    ACTION_NAMES.clif_match,
+]
+
+preprocessor_compile_actions = [
+    ACTION_NAMES.c_compile,
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.preprocess_assemble,
+    ACTION_NAMES.cpp_header_parsing,
+    ACTION_NAMES.cpp_module_compile,
+    ACTION_NAMES.clif_match,
+]
+
+codegen_compile_actions = [
+    ACTION_NAMES.c_compile,
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.assemble,
+    ACTION_NAMES.preprocess_assemble,
+    ACTION_NAMES.cpp_module_codegen,
+    ACTION_NAMES.lto_backend,
+]
+
+all_link_actions = [
+    ACTION_NAMES.cpp_link_executable,
+    ACTION_NAMES.cpp_link_dynamic_library,
+    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+]
+
+def _windows_msvc_impl(ctx):
+    toolchain_identifier = "msvc_x64"
+    host_system_name = "local"
+    target_system_name = "local"
+    target_cpu = "x64_windows"
+    target_libc = "msvcrt"
+    compiler = "msvc-cl"
+    abi_version = "local"
+    abi_libc_version = "local"
+    cc_target_os = None
+    builtin_sysroot = None
+
+    cxx_builtin_include_directories = [
+        # This is a workaround for https://github.com/bazelbuild/bazel/issues/5087.
+        "C:\\botcode\\w",
+        "c:/tools/msys64/usr/",
+        "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE",
+        "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt",
+    ]
+
+    cpp_link_nodeps_dynamic_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+        implies = [
+            "nologo",
+            "shared_flag",
+            "linkstamps",
+            "output_execpath_flags",
+            "input_param_flags",
+            "user_link_flags",
+            "default_link_flags",
+            "linker_subsystem_flag",
+            "linker_param_file",
+            "msvc_env",
+            "no_stripping",
+            "has_configured_linker_path",
+            "def_file",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/link.exe")],
+    )
+
+    cpp_link_static_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_static_library,
+        implies = [
+            "nologo",
+            "archiver_flags",
+            "input_param_flags",
+            "linker_param_file",
+            "msvc_env",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/lib.exe")],
+    )
+
+    assemble_action = action_config(
+        action_name = ACTION_NAMES.assemble,
+        implies = [
+            "compiler_input_flags",
+            "compiler_output_flags",
+            "nologo",
+            "msvc_env",
+            "sysroot",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/ml64.exe")],
+    )
+
+    preprocess_assemble_action = action_config(
+        action_name = ACTION_NAMES.preprocess_assemble,
+        implies = [
+            "compiler_input_flags",
+            "compiler_output_flags",
+            "nologo",
+            "msvc_env",
+            "sysroot",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/ml64.exe")],
+    )
+
+    c_compile_action = action_config(
+        action_name = ACTION_NAMES.c_compile,
+        implies = [
+            "compiler_input_flags",
+            "compiler_output_flags",
+            "default_compile_flags",
+            "nologo",
+            "msvc_env",
+            "parse_showincludes",
+            "user_compile_flags",
+            "sysroot",
+            "unfiltered_compile_flags",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe")],
+    )
+
+    cpp_compile_action = action_config(
+        action_name = ACTION_NAMES.cpp_compile,
+        implies = [
+            "compiler_input_flags",
+            "compiler_output_flags",
+            "default_compile_flags",
+            "nologo",
+            "msvc_env",
+            "parse_showincludes",
+            "user_compile_flags",
+            "sysroot",
+            "unfiltered_compile_flags",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe")],
+    )
+
+    cpp_link_executable_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_executable,
+        implies = [
+            "nologo",
+            "linkstamps",
+            "output_execpath_flags",
+            "input_param_flags",
+            "user_link_flags",
+            "default_link_flags",
+            "linker_subsystem_flag",
+            "linker_param_file",
+            "msvc_env",
+            "no_stripping",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/link.exe")],
+    )
+
+    cpp_link_dynamic_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_dynamic_library,
+        implies = [
+            "nologo",
+            "shared_flag",
+            "linkstamps",
+            "output_execpath_flags",
+            "input_param_flags",
+            "user_link_flags",
+            "default_link_flags",
+            "linker_subsystem_flag",
+            "linker_param_file",
+            "msvc_env",
+            "no_stripping",
+            "has_configured_linker_path",
+            "def_file",
+        ],
+        tools = [tool(path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/link.exe")],
+    )
+
+    action_configs = [
+        assemble_action,
+        preprocess_assemble_action,
+        c_compile_action,
+        cpp_compile_action,
+        cpp_link_executable_action,
+        cpp_link_dynamic_library_action,
+        cpp_link_nodeps_dynamic_library_action,
+        cpp_link_static_library_action,
+    ]
+
+    msvc_link_env_feature = feature(
+        name = "msvc_link_env",
+        env_sets = [
+            env_set(
+                actions = all_link_actions +
+                          [ACTION_NAMES.cpp_link_static_library],
+                env_entries = [env_entry(key = "LIB", value = "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\LIB\\amd64;C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.10240.0\\ucrt\\x64;C:\\Program Files (x86)\\Windows Kits\\8.1\\lib\\winv6.3\\um\\x64;")],
+            ),
+        ],
+    )
+
+    shared_flag_feature = feature(
+        name = "shared_flag",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                ],
+                flag_groups = [flag_group(flags = ["/DLL"])],
+            ),
+        ],
+    )
+
+    determinism_feature = feature(
+        name = "determinism",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [
+                    flag_group(
+                        flags = [
+                            "/wd4117",
+                            "-D__DATE__=\"redacted\"",
+                            "-D__TIMESTAMP__=\"redacted\"",
+                            "-D__TIME__=\"redacted\"",
+                        ],
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    sysroot_feature = feature(
+        name = "sysroot",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.cpp_link_executable,
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["--sysroot=%{sysroot}"],
+                        iterate_over = "sysroot",
+                        expand_if_available = "sysroot",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    unfiltered_compile_flags_feature = feature(
+        name = "unfiltered_compile_flags",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["%{unfiltered_compile_flags}"],
+                        iterate_over = "unfiltered_compile_flags",
+                        expand_if_available = "unfiltered_compile_flags",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    copy_dynamic_libraries_to_binary_feature = feature(name = "copy_dynamic_libraries_to_binary")
+
+    input_param_flags_feature = feature(
+        name = "input_param_flags",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["/IMPLIB:%{interface_library_output_path}"],
+                        expand_if_available = "interface_library_output_path",
+                    ),
+                ],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["%{libopts}"],
+                        iterate_over = "libopts",
+                        expand_if_available = "libopts",
+                    ),
+                ],
+            ),
+            flag_set(
+                actions = all_link_actions +
+                          [ACTION_NAMES.cpp_link_static_library],
+                flag_groups = [
+                    flag_group(
+                        iterate_over = "libraries_to_link",
+                        flag_groups = [
+                            flag_group(
+                                iterate_over = "libraries_to_link.object_files",
+                                flag_groups = [flag_group(flags = ["%{libraries_to_link.object_files}"])],
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "object_file_group",
+                                ),
+                            ),
+                            flag_group(
+                                flag_groups = [flag_group(flags = ["%{libraries_to_link.name}"])],
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "object_file",
+                                ),
+                            ),
+                            flag_group(
+                                flag_groups = [flag_group(flags = ["%{libraries_to_link.name}"])],
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "interface_library",
+                                ),
+                            ),
+                            flag_group(
+                                flag_groups = [
+                                    flag_group(
+                                        flags = ["%{libraries_to_link.name}"],
+                                        expand_if_false = "libraries_to_link.is_whole_archive",
+                                    ),
+                                    flag_group(
+                                        flags = ["/WHOLEARCHIVE:%{libraries_to_link.name}"],
+                                        expand_if_true = "libraries_to_link.is_whole_archive",
+                                    ),
+                                ],
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "static_library",
+                                ),
+                            ),
+                        ],
+                        expand_if_available = "libraries_to_link",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    fastbuild_feature = feature(
+        name = "fastbuild",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/Od", "/Z7"])],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["/DEBUG:FASTLINK", "/INCREMENTAL:NO"],
+                    ),
+                ],
+            ),
+        ],
+        implies = ["generate_pdb_file"],
+    )
+
+    user_compile_flags_feature = feature(
+        name = "user_compile_flags",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["%{user_compile_flags}"],
+                        iterate_over = "user_compile_flags",
+                        expand_if_available = "user_compile_flags",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    archiver_flags_feature = feature(
+        name = "archiver_flags",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.cpp_link_static_library],
+                flag_groups = [
+                    flag_group(
+                        flags = ["/OUT:%{output_execpath}"],
+                        expand_if_available = "output_execpath",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    default_link_flags_feature = feature(
+        name = "default_link_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [flag_group(flags = ["/MACHINE:X64"])],
+            ),
+        ],
+    )
+
+    static_link_msvcrt_feature = feature(name = "static_link_msvcrt")
+
+    dynamic_link_msvcrt_debug_feature = feature(
+        name = "dynamic_link_msvcrt_debug",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/MDd"])],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [flag_group(flags = ["/DEFAULTLIB:msvcrtd.lib"])],
+            ),
+        ],
+        requires = [feature_set(features = ["dbg"])],
+    )
+
+    dbg_feature = feature(
+        name = "dbg",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/Od", "/Z7"])],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["/DEBUG:FULL", "/INCREMENTAL:NO"],
+                    ),
+                ],
+            ),
+        ],
+        implies = ["generate_pdb_file"],
+    )
+
+    opt_feature = feature(
+        name = "opt",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/O2"])],
+            ),
+        ],
+        implies = ["frame_pointer"],
+    )
+
+    supports_interface_shared_libraries_feature = feature(
+        name = "supports_interface_shared_libraries",
+        enabled = True,
+    )
+
+    user_link_flags_feature = feature(
+        name = "user_link_flags",
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["%{user_link_flags}"],
+                        iterate_over = "user_link_flags",
+                        expand_if_available = "user_link_flags",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    default_compile_flags_feature = feature(
+        name = "default_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = [
+                            "/DCOMPILER_MSVC",
+                            "/DNOMINMAX",
+                            "/D_WIN32_WINNT=0x0601",
+                            "/D_CRT_SECURE_NO_DEPRECATE",
+                            "/D_CRT_SECURE_NO_WARNINGS",
+                            "/bigobj",
+                            "/Zm500",
+                            "/EHsc",
+                            "/wd4351",
+                            "/wd4291",
+                            "/wd4250",
+                            "/wd4996",
+                        ],
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    msvc_compile_env_feature = feature(
+        name = "msvc_compile_env",
+        env_sets = [
+            env_set(
+                actions = [
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                ],
+                env_entries = [env_entry(key = "INCLUDE", value = "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE;C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt;C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared;C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um;C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt;")],
+            ),
+        ],
+    )
+
+    preprocessor_defines_feature = feature(
+        name = "preprocessor_defines",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["/D%{preprocessor_defines}"],
+                        iterate_over = "preprocessor_defines",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    generate_pdb_file_feature = feature(
+        name = "generate_pdb_file",
+        requires = [
+            feature_set(features = ["dbg"]),
+            feature_set(features = ["fastbuild"]),
+        ],
+    )
+
+    output_execpath_flags_feature = feature(
+        name = "output_execpath_flags",
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["/OUT:%{output_execpath}"],
+                        expand_if_available = "output_execpath",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    dynamic_link_msvcrt_no_debug_feature = feature(
+        name = "dynamic_link_msvcrt_no_debug",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/MD"])],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [flag_group(flags = ["/DEFAULTLIB:msvcrt.lib"])],
+            ),
+        ],
+        requires = [
+            feature_set(features = ["fastbuild"]),
+            feature_set(features = ["opt"]),
+        ],
+    )
+
+    disable_assertions_feature = feature(
+        name = "disable_assertions",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/DNDEBUG"])],
+                with_features = [with_feature_set(features = ["opt"])],
+            ),
+        ],
+    )
+
+    has_configured_linker_path_feature = feature(name = "has_configured_linker_path")
+
+    supports_dynamic_linker_feature = feature(name = "supports_dynamic_linker", enabled = True)
+
+    no_stripping_feature = feature(name = "no_stripping")
+
+    linker_param_file_feature = feature(
+        name = "linker_param_file",
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions +
+                          [ACTION_NAMES.cpp_link_static_library],
+                flag_groups = [
+                    flag_group(
+                        flags = ["@%{linker_param_file}"],
+                        expand_if_available = "linker_param_file",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    ignore_noisy_warnings_feature = feature(
+        name = "ignore_noisy_warnings",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.cpp_link_static_library],
+                flag_groups = [flag_group(flags = ["/ignore:4221"])],
+            ),
+        ],
+    )
+
+    no_legacy_features_feature = feature(name = "no_legacy_features")
+
+    parse_showincludes_feature = feature(
+        name = "parse_showincludes",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                ],
+                flag_groups = [flag_group(flags = ["/showIncludes"])],
+            ),
+        ],
+    )
+
+    static_link_msvcrt_no_debug_feature = feature(
+        name = "static_link_msvcrt_no_debug",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/MT"])],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [flag_group(flags = ["/DEFAULTLIB:libcmt.lib"])],
+            ),
+        ],
+        requires = [
+            feature_set(features = ["fastbuild"]),
+            feature_set(features = ["opt"]),
+        ],
+    )
+
+    treat_warnings_as_errors_feature = feature(
+        name = "treat_warnings_as_errors",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/WX"])],
+            ),
+        ],
+    )
+
+    windows_export_all_symbols_feature = feature(name = "windows_export_all_symbols")
+
+    no_windows_export_all_symbols_feature = feature(name = "no_windows_export_all_symbols")
+
+    include_paths_feature = feature(
+        name = "include_paths",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["/I%{quote_include_paths}"],
+                        iterate_over = "quote_include_paths",
+                    ),
+                    flag_group(
+                        flags = ["/I%{include_paths}"],
+                        iterate_over = "include_paths",
+                    ),
+                    flag_group(
+                        flags = ["/I%{system_include_paths}"],
+                        iterate_over = "system_include_paths",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    linkstamps_feature = feature(
+        name = "linkstamps",
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["%{linkstamp_paths}"],
+                        iterate_over = "linkstamp_paths",
+                        expand_if_available = "linkstamp_paths",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    targets_windows_feature = feature(
+        name = "targets_windows",
+        enabled = True,
+        implies = ["copy_dynamic_libraries_to_binary"],
+    )
+
+    linker_subsystem_flag_feature = feature(
+        name = "linker_subsystem_flag",
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [flag_group(flags = ["/SUBSYSTEM:CONSOLE"])],
+            ),
+        ],
+    )
+
+    static_link_msvcrt_debug_feature = feature(
+        name = "static_link_msvcrt_debug",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/MTd"])],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [flag_group(flags = ["/DEFAULTLIB:libcmtd.lib"])],
+            ),
+        ],
+        requires = [feature_set(features = ["dbg"])],
+    )
+
+    frame_pointer_feature = feature(
+        name = "frame_pointer",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/Oy-"])],
+            ),
+        ],
+    )
+
+    compiler_output_flags_feature = feature(
+        name = "compiler_output_flags",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.assemble],
+                flag_groups = [
+                    flag_group(
+                        flag_groups = [
+                            flag_group(
+                                flags = ["/Fo%{output_file}", "/Zi"],
+                                expand_if_available = "output_file",
+                                expand_if_not_available = "output_assembly_file",
+                            ),
+                        ],
+                        expand_if_not_available = "output_preprocess_file",
+                    ),
+                ],
+            ),
+            flag_set(
+                actions = [
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flag_groups = [
+                            flag_group(
+                                flags = ["/Fo%{output_file}"],
+                                expand_if_not_available = "output_preprocess_file",
+                            ),
+                        ],
+                        expand_if_available = "output_file",
+                        expand_if_not_available = "output_assembly_file",
+                    ),
+                    flag_group(
+                        flag_groups = [
+                            flag_group(
+                                flags = ["/Fa%{output_file}"],
+                                expand_if_available = "output_assembly_file",
+                            ),
+                        ],
+                        expand_if_available = "output_file",
+                    ),
+                    flag_group(
+                        flag_groups = [
+                            flag_group(
+                                flags = ["/P", "/Fi%{output_file}"],
+                                expand_if_available = "output_preprocess_file",
+                            ),
+                        ],
+                        expand_if_available = "output_file",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    nologo_feature = feature(
+        name = "nologo",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.cpp_link_executable,
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                    ACTION_NAMES.cpp_link_static_library,
+                ],
+                flag_groups = [flag_group(flags = ["/nologo"])],
+            ),
+        ],
+    )
+
+    smaller_binary_feature = feature(
+        name = "smaller_binary",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [flag_group(flags = ["/Gy", "/Gw"])],
+                with_features = [with_feature_set(features = ["opt"])],
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [flag_group(flags = ["/OPT:ICF", "/OPT:REF"])],
+                with_features = [with_feature_set(features = ["opt"])],
+            ),
+        ],
+    )
+
+    compiler_input_flags_feature = feature(
+        name = "compiler_input_flags",
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["/c", "%{source_file}"],
+                        expand_if_available = "source_file",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    def_file_feature = feature(
+        name = "def_file",
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["/DEF:%{def_file_path}", "/ignore:4070"],
+                        expand_if_available = "def_file_path",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    msvc_env_feature = feature(
+        name = "msvc_env",
+        env_sets = [
+            env_set(
+                actions = [
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.cpp_link_executable,
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                    ACTION_NAMES.cpp_link_static_library,
+                ],
+                env_entries = [
+                    env_entry(key = "PATH", value = "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\amd64;C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework64\\;C:\\Program Files (x86)\\Windows Kits\\8.1\\bin\\x64;C:\\Program Files (x86)\\Windows Kits\\8.1\\bin\\x86;;C:\\Windows\\system32"),
+                    env_entry(key = "TMP", value = "C:\\Users\\ContainerAdministrator\\AppData\\Local\\Temp"),
+                    env_entry(key = "TEMP", value = "C:\\Users\\ContainerAdministrator\\AppData\\Local\\Temp"),
+                ],
+            ),
+        ],
+        implies = ["msvc_compile_env", "msvc_link_env"],
+    )
+
+    features = [
+        no_legacy_features_feature,
+        nologo_feature,
+        has_configured_linker_path_feature,
+        no_stripping_feature,
+        targets_windows_feature,
+        copy_dynamic_libraries_to_binary_feature,
+        default_compile_flags_feature,
+        msvc_env_feature,
+        msvc_compile_env_feature,
+        msvc_link_env_feature,
+        include_paths_feature,
+        preprocessor_defines_feature,
+        parse_showincludes_feature,
+        generate_pdb_file_feature,
+        shared_flag_feature,
+        linkstamps_feature,
+        output_execpath_flags_feature,
+        archiver_flags_feature,
+        input_param_flags_feature,
+        linker_subsystem_flag_feature,
+        user_link_flags_feature,
+        default_link_flags_feature,
+        linker_param_file_feature,
+        static_link_msvcrt_feature,
+        static_link_msvcrt_no_debug_feature,
+        dynamic_link_msvcrt_no_debug_feature,
+        static_link_msvcrt_debug_feature,
+        dynamic_link_msvcrt_debug_feature,
+        dbg_feature,
+        fastbuild_feature,
+        opt_feature,
+        frame_pointer_feature,
+        disable_assertions_feature,
+        determinism_feature,
+        treat_warnings_as_errors_feature,
+        smaller_binary_feature,
+        ignore_noisy_warnings_feature,
+        user_compile_flags_feature,
+        sysroot_feature,
+        unfiltered_compile_flags_feature,
+        compiler_output_flags_feature,
+        compiler_input_flags_feature,
+        def_file_feature,
+        windows_export_all_symbols_feature,
+        no_windows_export_all_symbols_feature,
+        supports_dynamic_linker_feature,
+        supports_interface_shared_libraries_feature,
+    ]
+
+    artifact_name_patterns = [
+        artifact_name_pattern(
+            category_name = "object_file",
+            prefix = "",
+            extension = ".obj",
+        ),
+        artifact_name_pattern(
+            category_name = "static_library",
+            prefix = "",
+            extension = ".lib",
+        ),
+        artifact_name_pattern(
+            category_name = "alwayslink_static_library",
+            prefix = "",
+            extension = ".lo.lib",
+        ),
+        artifact_name_pattern(
+            category_name = "executable",
+            prefix = "",
+            extension = ".exe",
+        ),
+        artifact_name_pattern(
+            category_name = "dynamic_library",
+            prefix = "",
+            extension = ".dll",
+        ),
+        artifact_name_pattern(
+            category_name = "interface_library",
+            prefix = "",
+            extension = ".if.lib",
+        ),
+    ]
+
+    make_variables = []
+
+    tool_paths = [
+        tool_path(name = "ar", path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/lib.exe"),
+        tool_path(name = "ml", path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/ml64.exe"),
+        tool_path(name = "cpp", path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe"),
+        tool_path(name = "gcc", path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe"),
+        tool_path(name = "gcov", path = "wrapper/bin/msvc_nop.bat"),
+        tool_path(name = "ld", path = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/link.exe"),
+        tool_path(name = "nm", path = "wrapper/bin/msvc_nop.bat"),
+        tool_path(
+            name = "objcopy",
+            path = "wrapper/bin/msvc_nop.bat",
+        ),
+        tool_path(
+            name = "objdump",
+            path = "wrapper/bin/msvc_nop.bat",
+        ),
+        tool_path(
+            name = "strip",
+            path = "wrapper/bin/msvc_nop.bat",
+        ),
+    ]
+
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        features = features,
+        action_configs = action_configs,
+        artifact_name_patterns = artifact_name_patterns,
+        cxx_builtin_include_directories = cxx_builtin_include_directories,
+        toolchain_identifier = toolchain_identifier,
+        host_system_name = host_system_name,
+        target_system_name = target_system_name,
+        target_cpu = target_cpu,
+        target_libc = target_libc,
+        compiler = compiler,
+        abi_version = abi_version,
+        abi_libc_version = abi_libc_version,
+        tool_paths = tool_paths,
+        make_variables = make_variables,
+        builtin_sysroot = builtin_sysroot,
+        cc_target_os = None,
+    )
+
+def _windows_msys_mingw_impl(ctx):
+    toolchain_identifier = "msys_x64_mingw"
+    host_system_name = "local"
+    target_system_name = "local"
+    target_cpu = "x64_windows"
+    target_libc = "mingw"
+    compiler = "mingw-gcc"
+    abi_version = "local"
+    abi_libc_version = "local"
+    cc_target_os = None
+    builtin_sysroot = None
+    action_configs = []
+
+    targets_windows_feature = feature(
+        name = "targets_windows",
+        implies = ["copy_dynamic_libraries_to_binary"],
+        enabled = True,
+    )
+
+    copy_dynamic_libraries_to_binary_feature = feature(name = "copy_dynamic_libraries_to_binary")
+
+    gcc_env_feature = feature(
+        name = "gcc_env",
+        enabled = True,
+        env_sets = [
+            env_set(
+                actions = [
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.cpp_link_executable,
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                    ACTION_NAMES.cpp_link_static_library,
+                ],
+                env_entries = [
+                    env_entry(key = "PATH", value = "c:/tools/msys64/mingw64/bin"),
+                ],
+            ),
+        ],
+    )
+
+    msys_mingw_flags = [
+        "-std=gnu++0x",
+    ]
+    msys_mingw_link_flags = [
+        "-lstdc++",
+    ]
+
+    default_compile_flags_feature = feature(
+        name = "default_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+            ),
+            flag_set(
+                actions = [
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = ([flag_group(flags = msys_mingw_flags)] if msys_mingw_flags else []),
+            ),
+        ],
+    )
+
+    default_link_flags_feature = feature(
+        name = "default_link_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = ([flag_group(flags = msys_mingw_link_flags)] if msys_mingw_link_flags else []),
+            ),
+        ],
+    )
+
+    supports_dynamic_linker_feature = feature(name = "supports_dynamic_linker", enabled = True)
+
+    features = [
+        targets_windows_feature,
+        copy_dynamic_libraries_to_binary_feature,
+        gcc_env_feature,
+        default_compile_flags_feature,
+        default_link_flags_feature,
+        supports_dynamic_linker_feature,
+    ]
+
+    cxx_builtin_include_directories = [
+        # This is a workaround for https://github.com/bazelbuild/bazel/issues/5087.
+        "C:\\botcode\\w",
+        "c:/tools/msys64/mingw64/",
+        "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE",
+        "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt",
+    ]
+
+    artifact_name_patterns = [
+        artifact_name_pattern(
+            category_name = "executable",
+            prefix = "",
+            extension = ".exe",
+        ),
+    ]
+
+    make_variables = []
+    tool_paths = [
+        tool_path(name = "ar", path = "c:/tools/msys64/mingw64/bin/ar"),
+        tool_path(name = "compat-ld", path = "c:/tools/msys64/mingw64/bin/ld"),
+        tool_path(name = "cpp", path = "c:/tools/msys64/mingw64/bin/cpp"),
+        tool_path(name = "dwp", path = "c:/tools/msys64/mingw64/bin/dwp"),
+        tool_path(name = "gcc", path = "c:/tools/msys64/mingw64/bin/gcc"),
+        tool_path(name = "gcov", path = "c:/tools/msys64/mingw64/bin/gcov"),
+        tool_path(name = "ld", path = "c:/tools/msys64/mingw64/bin/ld"),
+        tool_path(name = "nm", path = "c:/tools/msys64/mingw64/bin/nm"),
+        tool_path(name = "objcopy", path = "c:/tools/msys64/mingw64/bin/objcopy"),
+        tool_path(name = "objdump", path = "c:/tools/msys64/mingw64/bin/objdump"),
+        tool_path(name = "strip", path = "c:/tools/msys64/mingw64/bin/strip"),
+    ]
+
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        features = features,
+        action_configs = action_configs,
+        artifact_name_patterns = artifact_name_patterns,
+        cxx_builtin_include_directories = cxx_builtin_include_directories,
+        toolchain_identifier = toolchain_identifier,
+        host_system_name = host_system_name,
+        target_system_name = target_system_name,
+        target_cpu = target_cpu,
+        target_libc = target_libc,
+        compiler = compiler,
+        abi_version = abi_version,
+        abi_libc_version = abi_libc_version,
+        tool_paths = tool_paths,
+        make_variables = make_variables,
+        builtin_sysroot = builtin_sysroot,
+        cc_target_os = cc_target_os,
+    )
+
+def _armeabi_impl(ctx):
+    toolchain_identifier = "stub_armeabi-v7a"
+    host_system_name = "armeabi-v7a"
+    target_system_name = "armeabi-v7a"
+    target_cpu = "armeabi-v7a"
+    target_libc = "armeabi-v7a"
+    compiler = "compiler"
+    abi_version = "armeabi-v7a"
+    abi_libc_version = "armeabi-v7a"
+    cc_target_os = None
+    builtin_sysroot = None
+    action_configs = []
+
+    supports_pic_feature = feature(name = "supports_pic", enabled = True)
+    supports_dynamic_linker_feature = feature(name = "supports_dynamic_linker", enabled = True)
+    features = [supports_dynamic_linker_feature, supports_pic_feature]
+
+    cxx_builtin_include_directories = [
+        # This is a workaround for https://github.com/bazelbuild/bazel/issues/5087.
+        "C:\\botcode\\w",
+    ]
+    artifact_name_patterns = []
+    make_variables = []
+
+    tool_paths = [
+        tool_path(name = "ar", path = "/bin/false"),
+        tool_path(name = "compat-ld", path = "/bin/false"),
+        tool_path(name = "cpp", path = "/bin/false"),
+        tool_path(name = "dwp", path = "/bin/false"),
+        tool_path(name = "gcc", path = "/bin/false"),
+        tool_path(name = "gcov", path = "/bin/false"),
+        tool_path(name = "ld", path = "/bin/false"),
+        tool_path(name = "nm", path = "/bin/false"),
+        tool_path(name = "objcopy", path = "/bin/false"),
+        tool_path(name = "objdump", path = "/bin/false"),
+        tool_path(name = "strip", path = "/bin/false"),
+    ]
+
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        features = features,
+        action_configs = action_configs,
+        artifact_name_patterns = artifact_name_patterns,
+        cxx_builtin_include_directories = cxx_builtin_include_directories,
+        toolchain_identifier = toolchain_identifier,
+        host_system_name = host_system_name,
+        target_system_name = target_system_name,
+        target_cpu = target_cpu,
+        target_libc = target_libc,
+        compiler = compiler,
+        abi_version = abi_version,
+        abi_libc_version = abi_libc_version,
+        tool_paths = tool_paths,
+        make_variables = make_variables,
+        builtin_sysroot = builtin_sysroot,
+        cc_target_os = cc_target_os,
+    )
+
+def _impl(ctx):
+    if ctx.attr.cpu == "armeabi-v7a":
+        return _armeabi_impl(ctx)
+    elif ctx.attr.cpu == "x64_windows" and ctx.attr.compiler == "msvc-cl":
+        return _windows_msvc_impl(ctx)
+    elif ctx.attr.cpu == "x64_windows" and ctx.attr.compiler == "mingw-gcc":
+        return _windows_msys_mingw_impl(ctx)
+
+    tool_paths = [
+        tool_path(name = "ar", path = "c:/tools/msys64/usr/bin/ar"),
+        tool_path(name = "compat-ld", path = "c:/tools/msys64/usr/bin/ld"),
+        tool_path(name = "cpp", path = "c:/tools/msys64/usr/bin/cpp"),
+        tool_path(name = "dwp", path = "c:/tools/msys64/usr/bin/dwp"),
+        tool_path(name = "gcc", path = "c:/tools/msys64/usr/bin/gcc"),
+        tool_path(name = "gcov", path = "c:/tools/msys64/usr/bin/gcov"),
+        tool_path(name = "ld", path = "c:/tools/msys64/usr/bin/ld"),
+        tool_path(name = "nm", path = "c:/tools/msys64/usr/bin/nm"),
+        tool_path(name = "objcopy", path = "c:/tools/msys64/usr/bin/objcopy"),
+        tool_path(name = "objdump", path = "c:/tools/msys64/usr/bin/objdump"),
+        tool_path(name = "strip", path = "c:/tools/msys64/usr/bin/strip"),
+    ]
+
+    cxx_builtin_include_directories = [
+        # This is a workaround for https://github.com/bazelbuild/bazel/issues/5087.
+        "C:\\botcode\\w",
+        "c:/tools/msys64/usr/",
+        "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE",
+        "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um",
+        "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt",
+    ]
+
+    action_configs = []
+
+    compile_flags = [
+    ]
+
+    dbg_compile_flags = [
+    ]
+
+    opt_compile_flags = [
+    ]
+
+    cxx_flags = [
+        "-std=gnu++0x",
+    ]
+
+    link_flags = [
+        "-lstdc++",
+    ]
+
+    opt_link_flags = [
+    ]
+
+    unfiltered_compile_flags = [
+    ]
+
+    targets_windows_feature = feature(
+        name = "targets_windows",
+        implies = ["copy_dynamic_libraries_to_binary"],
+        enabled = True,
+    )
+
+    copy_dynamic_libraries_to_binary_feature = feature(name = "copy_dynamic_libraries_to_binary")
+
+    gcc_env_feature = feature(
+        name = "gcc_env",
+        enabled = True,
+        env_sets = [
+            env_set(
+                actions = [
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.cpp_link_executable,
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                    ACTION_NAMES.cpp_link_static_library,
+                ],
+                env_entries = [
+                    env_entry(key = "PATH", value = "c:/tools/msys64/usr/bin"),
+                ],
+            ),
+        ],
+    )
+
+    windows_features = [
+        targets_windows_feature,
+        copy_dynamic_libraries_to_binary_feature,
+        gcc_env_feature,
+    ]
+
+    supports_pic_feature = feature(
+        name = "supports_pic",
+        enabled = True,
+    )
+    supports_start_end_lib_feature = feature(
+        name = "supports_start_end_lib",
+        enabled = True,
+    )
+
+    default_compile_flags_feature = feature(
+        name = "default_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = ([flag_group(flags = compile_flags)] if compile_flags else []),
+            ),
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = ([flag_group(flags = dbg_compile_flags)] if dbg_compile_flags else []),
+                with_features = [with_feature_set(features = ["dbg"])],
+            ),
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = ([flag_group(flags = opt_compile_flags)] if opt_compile_flags else []),
+                with_features = [with_feature_set(features = ["opt"])],
+            ),
+            flag_set(
+                actions = [
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = ([flag_group(flags = cxx_flags)] if cxx_flags else []),
+            ),
+        ],
+    )
+
+    default_link_flags_feature = feature(
+        name = "default_link_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = ([flag_group(flags = link_flags)] if link_flags else []),
+            ),
+            flag_set(
+                actions = all_link_actions,
+                flag_groups = ([flag_group(flags = opt_link_flags)] if opt_link_flags else []),
+                with_features = [with_feature_set(features = ["opt"])],
+            ),
+        ],
+    )
+
+    dbg_feature = feature(name = "dbg")
+
+    opt_feature = feature(name = "opt")
+
+    sysroot_feature = feature(
+        name = "sysroot",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                    ACTION_NAMES.cpp_link_executable,
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["--sysroot=%{sysroot}"],
+                        expand_if_available = "sysroot",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    fdo_optimize_feature = feature(
+        name = "fdo_optimize",
+        flag_sets = [
+            flag_set(
+                actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
+                flag_groups = [
+                    flag_group(
+                        flags = [
+                            "-fprofile-use=%{fdo_profile_path}",
+                            "-fprofile-correction",
+                        ],
+                        expand_if_available = "fdo_profile_path",
+                    ),
+                ],
+            ),
+        ],
+        provides = ["profile"],
+    )
+
+    supports_dynamic_linker_feature = feature(name = "supports_dynamic_linker", enabled = True)
+
+    user_compile_flags_feature = feature(
+        name = "user_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = ["%{user_compile_flags}"],
+                        iterate_over = "user_compile_flags",
+                        expand_if_available = "user_compile_flags",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    unfiltered_compile_flags_feature = feature(
+        name = "unfiltered_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.assemble,
+                    ACTION_NAMES.preprocess_assemble,
+                    ACTION_NAMES.linkstamp_compile,
+                    ACTION_NAMES.c_compile,
+                    ACTION_NAMES.cpp_compile,
+                    ACTION_NAMES.cpp_header_parsing,
+                    ACTION_NAMES.cpp_module_compile,
+                    ACTION_NAMES.cpp_module_codegen,
+                    ACTION_NAMES.lto_backend,
+                    ACTION_NAMES.clif_match,
+                ],
+                flag_groups = ([flag_group(flags = unfiltered_compile_flags)] if unfiltered_compile_flags else []),
+            ),
+        ],
+    )
+
+    features = windows_features + [
+        supports_pic_feature,
+        default_compile_flags_feature,
+        default_link_flags_feature,
+        fdo_optimize_feature,
+        supports_dynamic_linker_feature,
+        dbg_feature,
+        opt_feature,
+        user_compile_flags_feature,
+        sysroot_feature,
+        unfiltered_compile_flags_feature,
+    ]
+
+    artifact_name_patterns = [
+        artifact_name_pattern(category_name = "executable", prefix = "", extension = ".exe"),
+    ]
+
+    make_variables = []
+
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        features = features,
+        action_configs = action_configs,
+        artifact_name_patterns = artifact_name_patterns,
+        cxx_builtin_include_directories = cxx_builtin_include_directories,
+        toolchain_identifier = "msys_x64",
+        host_system_name = "local",
+        target_system_name = "local",
+        target_cpu = "x64_windows",
+        target_libc = "msys",
+        compiler = "msys-gcc",
+        abi_version = "local",
+        abi_libc_version = "local",
+        tool_paths = tool_paths,
+        make_variables = make_variables,
+        builtin_sysroot = "",
+        cc_target_os = None,
+    )
+
+cc_toolchain_config = rule(
+    implementation = _impl,
+    attrs = {
+        "cpu": attr.string(mandatory = True),
+        "compiler": attr.string(),
+    },
+    provides = [CcToolchainConfigInfo],
+)
diff --git a/third_party/toolchains/bazel_0.23.2_rbe_windows/dummy_toolchain.bzl b/third_party/toolchains/bazel_0.23.2_rbe_windows/dummy_toolchain.bzl
new file mode 100644
index 0000000..45c0285
--- /dev/null
+++ b/third_party/toolchains/bazel_0.23.2_rbe_windows/dummy_toolchain.bzl
@@ -0,0 +1,23 @@
+# pylint: disable=g-bad-file-header
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""Skylark rule that stubs a toolchain."""
+
+def _dummy_toolchain_impl(ctx):
+    ctx = ctx  # unused argument
+    toolchain = platform_common.ToolchainInfo()
+    return [toolchain]
+
+dummy_toolchain = rule(_dummy_toolchain_impl, attrs = {})
diff --git a/tools/codegen/core/gen_static_metadata.py b/tools/codegen/core/gen_static_metadata.py
index ab288a0..5545e87 100755
--- a/tools/codegen/core/gen_static_metadata.py
+++ b/tools/codegen/core/gen_static_metadata.py
@@ -394,7 +394,7 @@
 
 def slice_def(i):
     return ('{&grpc_static_metadata_refcounts[%d],'
-            ' {{g_bytes+%d, %d}}}') % (i, id2strofs[i], len(all_strs[i]))
+            ' {{%d, g_bytes+%d}}}') % (i, len(all_strs[i]), id2strofs[i])
 
 
 # validate configuration
@@ -412,29 +412,19 @@
 print >> C, 'static uint8_t g_bytes[] = {%s};' % (','.join(
     '%d' % ord(c) for c in ''.join(all_strs)))
 print >> C
-print >> C, 'static void static_ref(void *unused) {}'
-print >> C, 'static void static_unref(void *unused) {}'
-print >> C, ('static const grpc_slice_refcount_vtable static_sub_vtable = '
-             '{static_ref, static_unref, grpc_slice_default_eq_impl, '
-             'grpc_slice_default_hash_impl};')
-print >> H, ('extern const grpc_slice_refcount_vtable '
-             'grpc_static_metadata_vtable;')
-print >> C, ('const grpc_slice_refcount_vtable grpc_static_metadata_vtable = '
-             '{static_ref, static_unref, grpc_static_slice_eq, '
-             'grpc_static_slice_hash};')
-print >> C, ('static grpc_slice_refcount static_sub_refcnt = '
-             '{&static_sub_vtable, &static_sub_refcnt};')
+print >> C, ('static grpc_slice_refcount static_sub_refcnt;')
 print >> H, ('extern grpc_slice_refcount '
              'grpc_static_metadata_refcounts[GRPC_STATIC_MDSTR_COUNT];')
 print >> C, ('grpc_slice_refcount '
              'grpc_static_metadata_refcounts[GRPC_STATIC_MDSTR_COUNT] = {')
 for i, elem in enumerate(all_strs):
-    print >> C, '  {&grpc_static_metadata_vtable, &static_sub_refcnt},'
+    print >> C, ('  grpc_slice_refcount(&static_sub_refcnt, '
+                 'grpc_slice_refcount::Type::STATIC), ')
 print >> C, '};'
 print >> C
 print >> H, '#define GRPC_IS_STATIC_METADATA_STRING(slice) \\'
-print >> H, ('  ((slice).refcount != NULL && (slice).refcount->vtable == '
-             '&grpc_static_metadata_vtable)')
+print >> H, ('  ((slice).refcount != NULL && (slice).refcount->GetType() == '
+             'grpc_slice_refcount::Type::STATIC)')
 print >> H
 print >> C, ('const grpc_slice grpc_static_slice_table[GRPC_STATIC_MDSTR_COUNT]'
              ' = {')
diff --git a/tools/codegen/core/gen_upb_api.sh b/tools/codegen/core/gen_upb_api.sh
index 2ca36b7..ff21c49 100755
--- a/tools/codegen/core/gen_upb_api.sh
+++ b/tools/codegen/core/gen_upb_api.sh
@@ -52,7 +52,9 @@
   "envoy/api/v2/cds.proto" \
   "envoy/api/v2/eds.proto" \
   "envoy/api/v2/endpoint/endpoint.proto" \
-  "envoy/service/discovery/v2/ads.proto")
+  "envoy/api/v2/endpoint/load_report.proto" \
+  "envoy/service/discovery/v2/ads.proto" \
+  "envoy/service/load_stats/v2/lrs.proto")
 
 for i in "${proto_files[@]}"
 do
diff --git a/tools/distrib/bazel_style.cfg b/tools/distrib/bazel_style.cfg
new file mode 100644
index 0000000..a5a1fea
--- /dev/null
+++ b/tools/distrib/bazel_style.cfg
@@ -0,0 +1,4 @@
+[style]
+based_on_style = google
+allow_split_before_dict_value = False
+spaces_around_default_or_named_assign = True
diff --git a/tools/distrib/format_bazel.sh b/tools/distrib/format_bazel.sh
new file mode 100755
index 0000000..ee23011
--- /dev/null
+++ b/tools/distrib/format_bazel.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# Copyright 2019 The 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.
+
+set=-ex
+
+VIRTUAL_ENV=bazel_format_virtual_environment
+
+CONFIG_PATH="$(dirname ${0})/bazel_style.cfg"
+
+python -m virtualenv ${VIRTUAL_ENV}
+PYTHON=${VIRTUAL_ENV}/bin/python
+"$PYTHON" -m pip install --upgrade pip==10.0.1
+"$PYTHON" -m pip install --upgrade futures
+"$PYTHON" -m pip install yapf==0.20.0
+
+pushd "$(dirname "${0}")/../.."
+FILES=$(find . -path ./third_party -prune -o -name '*.bzl' -print)
+echo "${FILES}" | xargs "$PYTHON" -m yapf -i --style="${CONFIG_PATH}"
+
+if ! which buildifier &>/dev/null; then
+    echo 'buildifer must be installed.' >/dev/stderr
+    exit 1
+fi
+
+echo "${FILES}" | xargs buildifier --type=bzl
+
+popd
diff --git a/tools/distrib/python/docgen.py b/tools/distrib/python/docgen.py
index de47c00..67d0054 100755
--- a/tools/distrib/python/docgen.py
+++ b/tools/distrib/python/docgen.py
@@ -44,7 +44,7 @@
 
 CONFIG = args.config
 SETUP_PATH = os.path.join(PROJECT_ROOT, 'setup.py')
-REQUIREMENTS_PATH = os.path.join(PROJECT_ROOT, 'requirements.txt')
+REQUIREMENTS_PATH = os.path.join(PROJECT_ROOT, 'requirements.bazel.txt')
 DOC_PATH = os.path.join(PROJECT_ROOT, 'doc/build')
 INCLUDE_PATH = os.path.join(PROJECT_ROOT, 'include')
 LIBRARY_PATH = os.path.join(PROJECT_ROOT, 'libs/{}'.format(CONFIG))
diff --git a/tools/distrib/python/grpcio_tools/grpc_version.py b/tools/distrib/python/grpcio_tools/grpc_version.py
index 7d2fc48..22d04c5 100644
--- a/tools/distrib/python/grpcio_tools/grpc_version.py
+++ b/tools/distrib/python/grpcio_tools/grpc_version.py
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/tools/distrib/python/grpcio_tools/grpc_version.py.template`!!!
 
-VERSION = '1.20.1'
+VERSION = '1.21.0.dev0'
diff --git a/tools/distrib/sanitize.sh b/tools/distrib/sanitize.sh
index fed1e64..13488b1 100755
--- a/tools/distrib/sanitize.sh
+++ b/tools/distrib/sanitize.sh
@@ -29,11 +29,11 @@
     fi
   fi
   CHANGED_FILES=$(eval $DIFF_COMMAND) ./tools/distrib/clang_format_code.sh
-  ./tools/distrib/check_copyright.py --fix --precommit
+  ./tools/distrib/check_copyright.py --precommit
   ./tools/distrib/check_trailing_newlines.sh
 else
   ./tools/buildgen/generate_projects.sh
   ./tools/distrib/clang_format_code.sh
-  ./tools/distrib/check_copyright.py --fix
+  ./tools/distrib/check_copyright.py
   ./tools/distrib/check_trailing_newlines.sh
 fi
diff --git a/tools/dockerfile/grpc_clang_format/Dockerfile b/tools/dockerfile/grpc_clang_format/Dockerfile
index 8e5edf7..876992f 100644
--- a/tools/dockerfile/grpc_clang_format/Dockerfile
+++ b/tools/dockerfile/grpc_clang_format/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 RUN apt-get update && apt-get -y install wget xz-utils
diff --git a/tools/dockerfile/grpc_clang_tidy/Dockerfile b/tools/dockerfile/grpc_clang_tidy/Dockerfile
index 2e0e683..a5abca0 100644
--- a/tools/dockerfile/grpc_clang_tidy/Dockerfile
+++ b/tools/dockerfile/grpc_clang_tidy/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 RUN apt-get update && apt-get -y install wget xz-utils
diff --git a/tools/dockerfile/interoptest/grpc_interop_cxx/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_cxx/Dockerfile
index 1fe64a4..e9f2a85 100644
--- a/tools/dockerfile/interoptest/grpc_interop_cxx/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_cxx/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/interoptest/grpc_interop_java/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_java/Dockerfile
index d168306..979c14d 100644
--- a/tools/dockerfile/interoptest/grpc_interop_java/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_java/Dockerfile
@@ -13,48 +13,22 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
-# Install JDK 8 and Git
+# Install JDK 8
 #
 RUN echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections && \
   echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee /etc/apt/sources.list.d/webupd8team-java.list && \
   echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list && \
-  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
-
-RUN apt-get update && apt-get -y install \
-      git \
-      libapr1 \
-      oracle-java8-installer \
-      && \
-    apt-get clean && rm -r /var/cache/oracle-jdk8-installer/
+  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886 && \
+  apt-get update && apt-get -y install oracle-java8-installer && \
+  apt-get clean && rm -r /var/cache/oracle-jdk8-installer/
 
 ENV JAVA_HOME /usr/lib/jvm/java-8-oracle
 ENV PATH $PATH:$JAVA_HOME/bin
 
 
-#====================
-# Python dependencies
-
-# Install dependencies
-
-RUN apt-get update && apt-get install -y \
-    python-all-dev \
-    python3-all-dev \
-    python-pip
-
-# Install Python packages from PyPI
-RUN pip install --upgrade pip==10.0.1
-RUN pip install virtualenv
-RUN pip install futures==2.2.0 enum34==1.0.4 protobuf==3.5.2.post1 six==1.10.0 twisted==17.5.0
-
-
-# Trigger download of as many Gradle artifacts as possible.
-RUN git clone --recursive --depth 1 https://github.com/grpc/grpc-java.git && \
-  cd grpc-java && \
-  ./gradlew :grpc-interop-testing:installDist -PskipCodegen=true && \
-  rm -r "$(pwd)"
 
 # Define the default command.
 CMD ["bash"]
+
diff --git a/tools/dockerfile/interoptest/grpc_interop_java/build_interop.sh b/tools/dockerfile/interoptest/grpc_interop_java/build_interop.sh
index 0e36c19..8e11546 100644
--- a/tools/dockerfile/interoptest/grpc_interop_java/build_interop.sh
+++ b/tools/dockerfile/interoptest/grpc_interop_java/build_interop.sh
@@ -16,16 +16,24 @@
 # Builds Java interop server and client in a base image.
 set -e
 
-mkdir -p /var/local/git
-git clone --recursive --depth 1 /var/local/jenkins/grpc-java /var/local/git/grpc-java
+cp -r /var/local/jenkins/grpc-java /tmp/grpc-java
 
 # copy service account keys if available
 cp -r /var/local/jenkins/service_account $HOME || true
 
-cd /var/local/git/grpc-java
-
+pushd /tmp/grpc-java
 ./gradlew :grpc-interop-testing:installDist -PskipCodegen=true
 
+mkdir -p /var/local/git/grpc-java/
+cp -r --parents -t /var/local/git/grpc-java/ \
+    interop-testing/build/install/ \
+    run-test-client.sh \
+    run-test-server.sh
+
+popd
+rm -r /tmp/grpc-java
+rm -r "$HOME/.gradle"
+
 # enable extra java logging
 mkdir -p /var/local/grpc_java_logging
 echo "handlers = java.util.logging.ConsoleHandler
diff --git a/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile
index d168306..979c14d 100644
--- a/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_java_oracle8/Dockerfile
@@ -13,48 +13,22 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
-# Install JDK 8 and Git
+# Install JDK 8
 #
 RUN echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections && \
   echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee /etc/apt/sources.list.d/webupd8team-java.list && \
   echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list && \
-  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
-
-RUN apt-get update && apt-get -y install \
-      git \
-      libapr1 \
-      oracle-java8-installer \
-      && \
-    apt-get clean && rm -r /var/cache/oracle-jdk8-installer/
+  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886 && \
+  apt-get update && apt-get -y install oracle-java8-installer && \
+  apt-get clean && rm -r /var/cache/oracle-jdk8-installer/
 
 ENV JAVA_HOME /usr/lib/jvm/java-8-oracle
 ENV PATH $PATH:$JAVA_HOME/bin
 
 
-#====================
-# Python dependencies
-
-# Install dependencies
-
-RUN apt-get update && apt-get install -y \
-    python-all-dev \
-    python3-all-dev \
-    python-pip
-
-# Install Python packages from PyPI
-RUN pip install --upgrade pip==10.0.1
-RUN pip install virtualenv
-RUN pip install futures==2.2.0 enum34==1.0.4 protobuf==3.5.2.post1 six==1.10.0 twisted==17.5.0
-
-
-# Trigger download of as many Gradle artifacts as possible.
-RUN git clone --recursive --depth 1 https://github.com/grpc/grpc-java.git && \
-  cd grpc-java && \
-  ./gradlew :grpc-interop-testing:installDist -PskipCodegen=true && \
-  rm -r "$(pwd)"
 
 # Define the default command.
 CMD ["bash"]
+
diff --git a/tools/dockerfile/interoptest/grpc_interop_java_oracle8/build_interop.sh b/tools/dockerfile/interoptest/grpc_interop_java_oracle8/build_interop.sh
index 4c5ba4b..77d3228 100644
--- a/tools/dockerfile/interoptest/grpc_interop_java_oracle8/build_interop.sh
+++ b/tools/dockerfile/interoptest/grpc_interop_java_oracle8/build_interop.sh
@@ -16,16 +16,24 @@
 # Builds Java interop server and client in a base image.
 set -e
 
-mkdir -p /var/local/git
-git clone --recursive --depth 1 /var/local/jenkins/grpc-java /var/local/git/grpc-java
+cp -r /var/local/jenkins/grpc-java /tmp/grpc-java
 
 # copy service account keys if available
 cp -r /var/local/jenkins/service_account $HOME || true
 
-cd /var/local/git/grpc-java
-
+pushd /tmp/grpc-java
 ./gradlew :grpc-interop-testing:installDist -PskipCodegen=true
 
+mkdir -p /var/local/git/grpc-java/
+cp -r --parents -t /var/local/git/grpc-java/ \
+    interop-testing/build/install/ \
+    run-test-client.sh \
+    run-test-server.sh
+
+popd
+rm -r /tmp/grpc-java
+rm -r "$HOME/.gradle"
+
 # enable extra java logging
 mkdir -p /var/local/grpc_java_logging
 echo "handlers = java.util.logging.ConsoleHandler
diff --git a/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile
index 96adf6e..283a99d 100644
--- a/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_node/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/interoptest/grpc_interop_nodepurejs/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_nodepurejs/Dockerfile
index 88e3cfa..fd089f1 100644
--- a/tools/dockerfile/interoptest/grpc_interop_nodepurejs/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_nodepurejs/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/interoptest/grpc_interop_php/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_php/Dockerfile
index c9c141e..04f0ac2 100644
--- a/tools/dockerfile/interoptest/grpc_interop_php/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_php/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/interoptest/grpc_interop_php7/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_php7/Dockerfile
index 8424139..01de0eb 100644
--- a/tools/dockerfile/interoptest/grpc_interop_php7/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_php7/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 #=================
diff --git a/tools/dockerfile/interoptest/grpc_interop_ruby/Dockerfile b/tools/dockerfile/interoptest/grpc_interop_ruby/Dockerfile
index cce5e86..8e605a8 100644
--- a/tools/dockerfile/interoptest/grpc_interop_ruby/Dockerfile
+++ b/tools/dockerfile/interoptest/grpc_interop_ruby/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/test/bazel/Dockerfile b/tools/dockerfile/test/bazel/Dockerfile
index 7dee005..2536fe2 100644
--- a/tools/dockerfile/test/bazel/Dockerfile
+++ b/tools/dockerfile/test/bazel/Dockerfile
@@ -52,9 +52,9 @@
 # Bazel installation
 
 RUN apt-get update && apt-get install -y wget && apt-get clean
-RUN wget https://github.com/bazelbuild/bazel/releases/download/0.20.0/bazel-0.20.0-installer-linux-x86_64.sh && \
-  bash ./bazel-0.20.0-installer-linux-x86_64.sh && \
-  rm bazel-0.20.0-installer-linux-x86_64.sh
+RUN wget https://github.com/bazelbuild/bazel/releases/download/0.23.2/bazel-0.23.2-installer-linux-x86_64.sh && \
+  bash ./bazel-0.23.2-installer-linux-x86_64.sh && \
+  rm bazel-0.23.2-installer-linux-x86_64.sh
 
 
 RUN mkdir -p /var/local/jenkins
diff --git a/tools/dockerfile/test/cxx_jessie_x64/Dockerfile b/tools/dockerfile/test/cxx_jessie_x64/Dockerfile
index c46f961..b79faa0 100644
--- a/tools/dockerfile/test/cxx_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/cxx_jessie_x64/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/test/fuzzer/Dockerfile b/tools/dockerfile/test/fuzzer/Dockerfile
index 14e4478..f9dd948 100644
--- a/tools/dockerfile/test/fuzzer/Dockerfile
+++ b/tools/dockerfile/test/fuzzer/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/test/node_jessie_x64/Dockerfile b/tools/dockerfile/test/node_jessie_x64/Dockerfile
index 0ef3210..7c0b86a 100644
--- a/tools/dockerfile/test/node_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/node_jessie_x64/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/test/php7_jessie_x64/Dockerfile b/tools/dockerfile/test/php7_jessie_x64/Dockerfile
index a32b764..5cca63d 100644
--- a/tools/dockerfile/test/php7_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/php7_jessie_x64/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 #=================
diff --git a/tools/dockerfile/test/php_jessie_x64/Dockerfile b/tools/dockerfile/test/php_jessie_x64/Dockerfile
index ebbbcf5..ee54263 100644
--- a/tools/dockerfile/test/php_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/php_jessie_x64/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/test/python_jessie_x64/Dockerfile b/tools/dockerfile/test/python_jessie_x64/Dockerfile
index 5ac3af8..367785f 100644
--- a/tools/dockerfile/test/python_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/python_jessie_x64/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/test/ruby_jessie_x64/Dockerfile b/tools/dockerfile/test/ruby_jessie_x64/Dockerfile
index ae46075..7670409 100644
--- a/tools/dockerfile/test/ruby_jessie_x64/Dockerfile
+++ b/tools/dockerfile/test/ruby_jessie_x64/Dockerfile
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 FROM debian:jessie
-RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
 
 
 # Install Git and basic packages.
diff --git a/tools/dockerfile/test/sanity/Dockerfile b/tools/dockerfile/test/sanity/Dockerfile
index 7a6fbfe..765bd72 100644
--- a/tools/dockerfile/test/sanity/Dockerfile
+++ b/tools/dockerfile/test/sanity/Dockerfile
@@ -98,9 +98,9 @@
 # Bazel installation
 
 RUN apt-get update && apt-get install -y wget && apt-get clean
-RUN wget https://github.com/bazelbuild/bazel/releases/download/0.20.0/bazel-0.20.0-installer-linux-x86_64.sh && \
-  bash ./bazel-0.20.0-installer-linux-x86_64.sh && \
-  rm bazel-0.20.0-installer-linux-x86_64.sh
+RUN wget https://github.com/bazelbuild/bazel/releases/download/0.23.2/bazel-0.23.2-installer-linux-x86_64.sh && \
+  bash ./bazel-0.23.2-installer-linux-x86_64.sh && \
+  rm bazel-0.23.2-installer-linux-x86_64.sh
 
 
 # Define the default command.
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index fac989c..d587492 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.20.1
+PROJECT_NUMBER         = 1.21.0-dev
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -927,16 +927,20 @@
 include/grpcpp/alarm.h \
 include/grpcpp/alarm_impl.h \
 include/grpcpp/channel.h \
+include/grpcpp/channel_impl.h \
 include/grpcpp/client_context.h \
 include/grpcpp/completion_queue.h \
 include/grpcpp/create_channel.h \
+include/grpcpp/create_channel_impl.h \
 include/grpcpp/create_channel_posix.h \
 include/grpcpp/create_channel_posix_impl.h \
 include/grpcpp/ext/health_check_service_server_builder_option.h \
 include/grpcpp/generic/async_generic_service.h \
 include/grpcpp/generic/generic_stub.h \
+include/grpcpp/generic/generic_stub_impl.h \
 include/grpcpp/grpcpp.h \
 include/grpcpp/health_check_service_interface.h \
+include/grpcpp/health_check_service_interface_impl.h \
 include/grpcpp/impl/call.h \
 include/grpcpp/impl/channel_argument_option.h \
 include/grpcpp/impl/client_unary_call.h \
@@ -955,6 +959,7 @@
 include/grpcpp/impl/codegen/client_interceptor.h \
 include/grpcpp/impl/codegen/client_unary_call.h \
 include/grpcpp/impl/codegen/completion_queue.h \
+include/grpcpp/impl/codegen/completion_queue_impl.h \
 include/grpcpp/impl/codegen/completion_queue_tag.h \
 include/grpcpp/impl/codegen/config.h \
 include/grpcpp/impl/codegen/config_protobuf.h \
@@ -965,6 +970,7 @@
 include/grpcpp/impl/codegen/intercepted_channel.h \
 include/grpcpp/impl/codegen/interceptor.h \
 include/grpcpp/impl/codegen/interceptor_common.h \
+include/grpcpp/impl/codegen/message_allocator.h \
 include/grpcpp/impl/codegen/metadata_map.h \
 include/grpcpp/impl/codegen/method_handler_impl.h \
 include/grpcpp/impl/codegen/proto_buffer_reader.h \
@@ -984,6 +990,7 @@
 include/grpcpp/impl/codegen/status_code_enum.h \
 include/grpcpp/impl/codegen/string_ref.h \
 include/grpcpp/impl/codegen/stub_options.h \
+include/grpcpp/impl/codegen/sync.h \
 include/grpcpp/impl/codegen/sync_stream.h \
 include/grpcpp/impl/codegen/time.h \
 include/grpcpp/impl/grpc_library.h \
@@ -995,25 +1002,34 @@
 include/grpcpp/impl/server_builder_option_impl.h \
 include/grpcpp/impl/server_builder_plugin.h \
 include/grpcpp/impl/server_initializer.h \
+include/grpcpp/impl/server_initializer_impl.h \
 include/grpcpp/impl/service_type.h \
 include/grpcpp/resource_quota.h \
+include/grpcpp/resource_quota_impl.h \
 include/grpcpp/security/auth_context.h \
 include/grpcpp/security/auth_metadata_processor.h \
+include/grpcpp/security/auth_metadata_processor_impl.h \
 include/grpcpp/security/credentials.h \
+include/grpcpp/security/credentials_impl.h \
 include/grpcpp/security/server_credentials.h \
+include/grpcpp/security/server_credentials_impl.h \
 include/grpcpp/server.h \
 include/grpcpp/server_builder.h \
+include/grpcpp/server_builder_impl.h \
 include/grpcpp/server_context.h \
+include/grpcpp/server_impl.h \
 include/grpcpp/server_posix.h \
 include/grpcpp/server_posix_impl.h \
 include/grpcpp/support/async_stream.h \
 include/grpcpp/support/async_unary_call.h \
 include/grpcpp/support/byte_buffer.h \
 include/grpcpp/support/channel_arguments.h \
+include/grpcpp/support/channel_arguments_impl.h \
 include/grpcpp/support/client_callback.h \
 include/grpcpp/support/client_interceptor.h \
 include/grpcpp/support/config.h \
 include/grpcpp/support/interceptor.h \
+include/grpcpp/support/message_allocator.h \
 include/grpcpp/support/proto_buffer_reader.h \
 include/grpcpp/support/proto_buffer_writer.h \
 include/grpcpp/support/server_callback.h \
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 5c48152..ab0968b 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.20.1
+PROJECT_NUMBER         = 1.21.0-dev
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -928,16 +928,20 @@
 include/grpcpp/alarm.h \
 include/grpcpp/alarm_impl.h \
 include/grpcpp/channel.h \
+include/grpcpp/channel_impl.h \
 include/grpcpp/client_context.h \
 include/grpcpp/completion_queue.h \
 include/grpcpp/create_channel.h \
+include/grpcpp/create_channel_impl.h \
 include/grpcpp/create_channel_posix.h \
 include/grpcpp/create_channel_posix_impl.h \
 include/grpcpp/ext/health_check_service_server_builder_option.h \
 include/grpcpp/generic/async_generic_service.h \
 include/grpcpp/generic/generic_stub.h \
+include/grpcpp/generic/generic_stub_impl.h \
 include/grpcpp/grpcpp.h \
 include/grpcpp/health_check_service_interface.h \
+include/grpcpp/health_check_service_interface_impl.h \
 include/grpcpp/impl/call.h \
 include/grpcpp/impl/channel_argument_option.h \
 include/grpcpp/impl/client_unary_call.h \
@@ -956,6 +960,7 @@
 include/grpcpp/impl/codegen/client_interceptor.h \
 include/grpcpp/impl/codegen/client_unary_call.h \
 include/grpcpp/impl/codegen/completion_queue.h \
+include/grpcpp/impl/codegen/completion_queue_impl.h \
 include/grpcpp/impl/codegen/completion_queue_tag.h \
 include/grpcpp/impl/codegen/config.h \
 include/grpcpp/impl/codegen/config_protobuf.h \
@@ -967,6 +972,7 @@
 include/grpcpp/impl/codegen/intercepted_channel.h \
 include/grpcpp/impl/codegen/interceptor.h \
 include/grpcpp/impl/codegen/interceptor_common.h \
+include/grpcpp/impl/codegen/message_allocator.h \
 include/grpcpp/impl/codegen/metadata_map.h \
 include/grpcpp/impl/codegen/method_handler_impl.h \
 include/grpcpp/impl/codegen/proto_buffer_reader.h \
@@ -986,6 +992,7 @@
 include/grpcpp/impl/codegen/status_code_enum.h \
 include/grpcpp/impl/codegen/string_ref.h \
 include/grpcpp/impl/codegen/stub_options.h \
+include/grpcpp/impl/codegen/sync.h \
 include/grpcpp/impl/codegen/sync_stream.h \
 include/grpcpp/impl/codegen/time.h \
 include/grpcpp/impl/grpc_library.h \
@@ -997,25 +1004,34 @@
 include/grpcpp/impl/server_builder_option_impl.h \
 include/grpcpp/impl/server_builder_plugin.h \
 include/grpcpp/impl/server_initializer.h \
+include/grpcpp/impl/server_initializer_impl.h \
 include/grpcpp/impl/service_type.h \
 include/grpcpp/resource_quota.h \
+include/grpcpp/resource_quota_impl.h \
 include/grpcpp/security/auth_context.h \
 include/grpcpp/security/auth_metadata_processor.h \
+include/grpcpp/security/auth_metadata_processor_impl.h \
 include/grpcpp/security/credentials.h \
+include/grpcpp/security/credentials_impl.h \
 include/grpcpp/security/server_credentials.h \
+include/grpcpp/security/server_credentials_impl.h \
 include/grpcpp/server.h \
 include/grpcpp/server_builder.h \
+include/grpcpp/server_builder_impl.h \
 include/grpcpp/server_context.h \
+include/grpcpp/server_impl.h \
 include/grpcpp/server_posix.h \
 include/grpcpp/server_posix_impl.h \
 include/grpcpp/support/async_stream.h \
 include/grpcpp/support/async_unary_call.h \
 include/grpcpp/support/byte_buffer.h \
 include/grpcpp/support/channel_arguments.h \
+include/grpcpp/support/channel_arguments_impl.h \
 include/grpcpp/support/client_callback.h \
 include/grpcpp/support/client_interceptor.h \
 include/grpcpp/support/config.h \
 include/grpcpp/support/interceptor.h \
+include/grpcpp/support/message_allocator.h \
 include/grpcpp/support/proto_buffer_reader.h \
 include/grpcpp/support/proto_buffer_writer.h \
 include/grpcpp/support/server_callback.h \
@@ -1045,6 +1061,7 @@
 src/core/lib/channel/handshaker_registry.h \
 src/core/lib/channel/status_util.h \
 src/core/lib/compression/algorithm_metadata.h \
+src/core/lib/compression/compression_args.h \
 src/core/lib/compression/compression_internal.h \
 src/core/lib/compression/message_compress.h \
 src/core/lib/compression/stream_compression.h \
@@ -1070,17 +1087,24 @@
 src/core/lib/gpr/tmpfile.h \
 src/core/lib/gpr/useful.h \
 src/core/lib/gprpp/abstract.h \
+src/core/lib/gprpp/arena.h \
 src/core/lib/gprpp/atomic.h \
 src/core/lib/gprpp/debug_location.h \
 src/core/lib/gprpp/fork.h \
+src/core/lib/gprpp/global_config.h \
+src/core/lib/gprpp/global_config_custom.h \
+src/core/lib/gprpp/global_config_env.h \
+src/core/lib/gprpp/global_config_generic.h \
 src/core/lib/gprpp/inlined_vector.h \
 src/core/lib/gprpp/manual_constructor.h \
+src/core/lib/gprpp/map.h \
 src/core/lib/gprpp/memory.h \
-src/core/lib/gprpp/mutex_lock.h \
 src/core/lib/gprpp/optional.h \
 src/core/lib/gprpp/orphanable.h \
+src/core/lib/gprpp/pair.h \
 src/core/lib/gprpp/ref_counted.h \
 src/core/lib/gprpp/ref_counted_ptr.h \
+src/core/lib/gprpp/sync.h \
 src/core/lib/gprpp/thd.h \
 src/core/lib/http/format_request.h \
 src/core/lib/http/httpcli.h \
@@ -1088,12 +1112,15 @@
 src/core/lib/iomgr/block_annotate.h \
 src/core/lib/iomgr/buffer_list.h \
 src/core/lib/iomgr/call_combiner.h \
+src/core/lib/iomgr/cfstream_handle.h \
 src/core/lib/iomgr/closure.h \
 src/core/lib/iomgr/combiner.h \
 src/core/lib/iomgr/dynamic_annotations.h \
 src/core/lib/iomgr/endpoint.h \
+src/core/lib/iomgr/endpoint_cfstream.h \
 src/core/lib/iomgr/endpoint_pair.h \
 src/core/lib/iomgr/error.h \
+src/core/lib/iomgr/error_cfstream.h \
 src/core/lib/iomgr/error_internal.h \
 src/core/lib/iomgr/ev_epoll1_linux.h \
 src/core/lib/iomgr/ev_epollex_linux.h \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 0fdab21..293a6e1 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -942,13 +942,19 @@
 src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h \
+src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc \
+src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc \
+src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc \
+src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc \
+src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc \
+src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h \
 src/core/ext/filters/client_channel/resolver/dns/native/README.md \
 src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc \
 src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc \
@@ -1089,6 +1095,8 @@
 src/core/lib/channel/status_util.h \
 src/core/lib/compression/algorithm_metadata.h \
 src/core/lib/compression/compression.cc \
+src/core/lib/compression/compression_args.cc \
+src/core/lib/compression/compression_args.h \
 src/core/lib/compression/compression_internal.cc \
 src/core/lib/compression/compression_internal.h \
 src/core/lib/compression/message_compress.cc \
@@ -1108,7 +1116,6 @@
 src/core/lib/gpr/README.md \
 src/core/lib/gpr/alloc.cc \
 src/core/lib/gpr/alloc.h \
-src/core/lib/gpr/arena.cc \
 src/core/lib/gpr/arena.h \
 src/core/lib/gpr/atm.cc \
 src/core/lib/gpr/cpu_iphone.cc \
@@ -1158,18 +1165,27 @@
 src/core/lib/gpr/wrap_memcpy.cc \
 src/core/lib/gprpp/README.md \
 src/core/lib/gprpp/abstract.h \
+src/core/lib/gprpp/arena.cc \
+src/core/lib/gprpp/arena.h \
 src/core/lib/gprpp/atomic.h \
 src/core/lib/gprpp/debug_location.h \
 src/core/lib/gprpp/fork.cc \
 src/core/lib/gprpp/fork.h \
+src/core/lib/gprpp/global_config.h \
+src/core/lib/gprpp/global_config_custom.h \
+src/core/lib/gprpp/global_config_env.cc \
+src/core/lib/gprpp/global_config_env.h \
+src/core/lib/gprpp/global_config_generic.h \
 src/core/lib/gprpp/inlined_vector.h \
 src/core/lib/gprpp/manual_constructor.h \
+src/core/lib/gprpp/map.h \
 src/core/lib/gprpp/memory.h \
-src/core/lib/gprpp/mutex_lock.h \
 src/core/lib/gprpp/optional.h \
 src/core/lib/gprpp/orphanable.h \
+src/core/lib/gprpp/pair.h \
 src/core/lib/gprpp/ref_counted.h \
 src/core/lib/gprpp/ref_counted_ptr.h \
+src/core/lib/gprpp/sync.h \
 src/core/lib/gprpp/thd.h \
 src/core/lib/gprpp/thd_posix.cc \
 src/core/lib/gprpp/thd_windows.cc \
@@ -1186,18 +1202,24 @@
 src/core/lib/iomgr/buffer_list.h \
 src/core/lib/iomgr/call_combiner.cc \
 src/core/lib/iomgr/call_combiner.h \
+src/core/lib/iomgr/cfstream_handle.cc \
+src/core/lib/iomgr/cfstream_handle.h \
 src/core/lib/iomgr/closure.h \
 src/core/lib/iomgr/combiner.cc \
 src/core/lib/iomgr/combiner.h \
 src/core/lib/iomgr/dynamic_annotations.h \
 src/core/lib/iomgr/endpoint.cc \
 src/core/lib/iomgr/endpoint.h \
+src/core/lib/iomgr/endpoint_cfstream.cc \
+src/core/lib/iomgr/endpoint_cfstream.h \
 src/core/lib/iomgr/endpoint_pair.h \
 src/core/lib/iomgr/endpoint_pair_posix.cc \
 src/core/lib/iomgr/endpoint_pair_uv.cc \
 src/core/lib/iomgr/endpoint_pair_windows.cc \
 src/core/lib/iomgr/error.cc \
 src/core/lib/iomgr/error.h \
+src/core/lib/iomgr/error_cfstream.cc \
+src/core/lib/iomgr/error_cfstream.h \
 src/core/lib/iomgr/error_internal.h \
 src/core/lib/iomgr/ev_epoll1_linux.cc \
 src/core/lib/iomgr/ev_epoll1_linux.h \
@@ -1233,6 +1255,7 @@
 src/core/lib/iomgr/iomgr_internal.h \
 src/core/lib/iomgr/iomgr_posix.cc \
 src/core/lib/iomgr/iomgr_posix.h \
+src/core/lib/iomgr/iomgr_posix_cfstream.cc \
 src/core/lib/iomgr/iomgr_uv.cc \
 src/core/lib/iomgr/iomgr_windows.cc \
 src/core/lib/iomgr/is_epollexclusive_available.cc \
@@ -1288,6 +1311,7 @@
 src/core/lib/iomgr/sys_epoll_wrapper.h \
 src/core/lib/iomgr/tcp_client.cc \
 src/core/lib/iomgr/tcp_client.h \
+src/core/lib/iomgr/tcp_client_cfstream.cc \
 src/core/lib/iomgr/tcp_client_custom.cc \
 src/core/lib/iomgr/tcp_client_posix.cc \
 src/core/lib/iomgr/tcp_client_posix.h \
diff --git a/tools/gce/create_linux_kokoro_performance_worker_from_image.sh b/tools/gce/create_linux_kokoro_performance_worker_from_image.sh
index 9031661..1b014ad 100755
--- a/tools/gce/create_linux_kokoro_performance_worker_from_image.sh
+++ b/tools/gce/create_linux_kokoro_performance_worker_from_image.sh
@@ -22,7 +22,7 @@
 
 CLOUD_PROJECT=grpc-testing
 ZONE=us-central1-b  # this zone allows 32core machines
-LATEST_PERF_WORKER_IMAGE=grpc-performance-kokoro-v4  # update if newer image exists
+LATEST_PERF_WORKER_IMAGE=grpc-performance-kokoro-v5  # update if newer image exists
 
 INSTANCE_NAME="${1:-grpc-kokoro-performance-server}"
 MACHINE_TYPE="${2:-n1-standard-32}"
diff --git a/tools/gce/linux_kokoro_performance_worker_init.sh b/tools/gce/linux_kokoro_performance_worker_init.sh
index 1bf2228..46061a0 100755
--- a/tools/gce/linux_kokoro_performance_worker_init.sh
+++ b/tools/gce/linux_kokoro_performance_worker_init.sh
@@ -219,6 +219,8 @@
 # See https://github.com/grpc/grpc/issues/17794
 sudo sed -i 's/APT::Periodic::Update-Package-Lists "1"/APT::Periodic::Update-Package-Lists "0"/' /etc/apt/apt.conf.d/10periodic
 sudo sed -i 's/APT::Periodic::AutocleanInterval "1"/APT::Periodic::AutocleanInterval "0"/' /etc/apt/apt.conf.d/10periodic
+sudo sed -i 's/APT::Periodic::Update-Package-Lists "1"/APT::Periodic::Update-Package-Lists "0"/' /etc/apt/apt.conf.d/20auto-upgrades
+sudo sed -i 's/APT::Periodic::Unattended-Upgrade "1"/APT::Periodic::Unattended-Upgrade "0"/' /etc/apt/apt.conf.d/20auto-upgrades
 
 # Restart for VM to pick up kernel update
 echo 'Successfully initialized the linux worker, going for reboot in 10 seconds'
diff --git a/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh b/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh
index 863b43a..93399f8 100755
--- a/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh
+++ b/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh
@@ -15,15 +15,9 @@
 
 set -ex
 
-# A temporary solution to give Kokoro credentials.
-# The file name 4321_grpc-testing-service needs to match auth_credential in
-# the build config.
-mkdir -p ${KOKORO_KEYSTORE_DIR}
-cp ${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json ${KOKORO_KEYSTORE_DIR}/4321_grpc-testing-service
-
 # Download bazel
 temp_dir="$(mktemp -d)"
-wget -q https://github.com/bazelbuild/bazel/releases/download/0.20.0/bazel-0.20.0-linux-x86_64 -O "${temp_dir}/bazel"
+wget -q https://github.com/bazelbuild/bazel/releases/download/0.23.2/bazel-0.23.2-linux-x86_64 -O "${temp_dir}/bazel"
 chmod 755 "${temp_dir}/bazel"
 export PATH="${temp_dir}:${PATH}"
 # This should show ${temp_dir}/bazel
@@ -45,6 +39,7 @@
   test \
   --invocation_id="${BAZEL_INVOCATION_ID}" \
   --workspace_status_command=tools/remote_build/workspace_status_kokoro.sh \
+  --google_credentials="${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json" \
   $@ \
   -- //test/... || FAILED="true"
 
diff --git a/tools/internal_ci/linux/grpc_bazel_rbe_incompatible_changes.sh b/tools/internal_ci/linux/grpc_bazel_rbe_incompatible_changes.sh
new file mode 100644
index 0000000..9c3712a
--- /dev/null
+++ b/tools/internal_ci/linux/grpc_bazel_rbe_incompatible_changes.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# Copyright 2017 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.
+
+set -ex
+
+# TODO(jtattermusch): use the latest version of bazel
+
+# Use --all_incompatible_changes to give an early warning about future
+# bazel incompatibilities.
+EXTRA_FLAGS="--config=opt --cache_test_results=no --all_incompatible_changes"
+github/grpc/tools/internal_ci/linux/grpc_bazel_on_foundry_base.sh "${EXTRA_FLAGS}"
diff --git a/tools/internal_ci/linux/grpc_publish_packages.cfg b/tools/internal_ci/linux/grpc_publish_packages.cfg
index dc9fe7d..54e03a9 100644
--- a/tools/internal_ci/linux/grpc_publish_packages.cfg
+++ b/tools/internal_ci/linux/grpc_publish_packages.cfg
@@ -24,3 +24,5 @@
     regex: "github/grpc/artifacts/**"
   }
 }
+
+gfile_resources: "/bigstore/grpc-testing-secrets/nuget_credentials/artifactory_grpc_nuget_dev_api_key"
diff --git a/tools/internal_ci/linux/grpc_publish_packages.sh b/tools/internal_ci/linux/grpc_publish_packages.sh
index 1449230..8768421 100755
--- a/tools/internal_ci/linux/grpc_publish_packages.sh
+++ b/tools/internal_ci/linux/grpc_publish_packages.sh
@@ -233,3 +233,16 @@
 )
 # Upload the new /index.xml
 gsutil -h "Content-Type:application/xml" cp "$NEW_INDEX" "$GCS_INDEX"
+
+# Upload C# nugets to the dev nuget feed
+pushd "$UNZIPPED_CSHARP_PACKAGES"
+docker pull mcr.microsoft.com/dotnet/core/sdk:2.1
+for nugetfile in *.nupkg
+do
+  echo "Going to push $nugetfile"
+  # use nuget from a docker container to push the nupkg
+  set +x  # IMPORTANT: avoid revealing the nuget api key by the command echo
+  docker run -v "$(pwd):/nugets:ro" --rm=true mcr.microsoft.com/dotnet/core/sdk:2.1 bash -c "dotnet nuget push /nugets/$nugetfile -k $(cat ${KOKORO_GFILE_DIR}/artifactory_grpc_nuget_dev_api_key) --source https://grpc.jfrog.io/grpc/api/nuget/v3/grpc-nuget-dev"
+  set -ex
+done
+popd
diff --git a/tools/internal_ci/macos/grpc_run_bazel_tests.sh b/tools/internal_ci/macos/grpc_run_bazel_tests.sh
index ef02a67..56fae8f 100644
--- a/tools/internal_ci/macos/grpc_run_bazel_tests.sh
+++ b/tools/internal_ci/macos/grpc_run_bazel_tests.sh
@@ -18,6 +18,13 @@
 # change to grpc repo root
 cd $(dirname $0)/../../..
 
+# Download bazel
+temp_dir="$(mktemp -d)"
+wget -q https://github.com/bazelbuild/bazel/releases/download/0.23.2/bazel-0.23.2-darwin-x86_64 -O "${temp_dir}/bazel"
+chmod 755 "${temp_dir}/bazel"
+export PATH="${temp_dir}:${PATH}"
+# This should show ${temp_dir}/bazel
+which bazel
 
 ./tools/run_tests/start_port_server.py
 
diff --git a/tools/internal_ci/windows/bazel_rbe.bat b/tools/internal_ci/windows/bazel_rbe.bat
new file mode 100644
index 0000000..8f2c534
--- /dev/null
+++ b/tools/internal_ci/windows/bazel_rbe.bat
@@ -0,0 +1,31 @@
+@rem Copyright 2019 gRPC authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem     http://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+
+@rem TODO(jtattermusch): make this generate less output
+choco install bazel -y --version 0.23.2 --limit-output
+
+cd github/grpc
+set PATH=C:\tools\msys64\usr\bin;C:\Python27;%PATH%
+
+@rem Generate a random UUID and store in "bazel_invocation_ids" artifact file
+powershell -Command "[guid]::NewGuid().ToString()" >%KOKORO_ARTIFACTS_DIR%/bazel_invocation_ids
+set /p BAZEL_INVOCATION_ID=<%KOKORO_ARTIFACTS_DIR%/bazel_invocation_ids
+
+@rem TODO(jtattermusch): windows RBE should be able to use the same credentials as Linux RBE.
+bazel --bazelrc=tools/remote_build/windows.bazelrc build --invocation_id="%BAZEL_INVOCATION_ID%" --workspace_status_command=tools/remote_build/workspace_status_kokoro.sh :all --incompatible_disallow_filetype=false --google_credentials=%KOKORO_GFILE_DIR%/rbe-windows-credentials.json
+set BAZEL_EXITCODE=%errorlevel%
+
+@rem TODO(jtattermusch): upload results to bigquery
+
+exit /b %BAZEL_EXITCODE%
diff --git a/tools/internal_ci/windows/pull_request/grpc_bazel_rbe_dbg.cfg b/tools/internal_ci/windows/pull_request/grpc_bazel_rbe_dbg.cfg
new file mode 100644
index 0000000..f958fed
--- /dev/null
+++ b/tools/internal_ci/windows/pull_request/grpc_bazel_rbe_dbg.cfg
@@ -0,0 +1,32 @@
+# Copyright 2019 The 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.
+
+# Config file for the internal CI (in protobuf text format)
+
+# Location of the continuous shell script in repository.
+build_file: "grpc/tools/internal_ci/windows/bazel_rbe.bat"
+
+timeout_mins: 60
+
+gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json"
+gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/resultstore_api_key"
+gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/rbe-windows-credentials.json"
+
+bazel_setting {
+  # In order for Kokoro to recognize this as a bazel build and publish the bazel resultstore link,
+  # the bazel_setting section needs to be present and "upsalite_frontend_address" needs to be
+  # set. The rest of configuration from bazel_setting is unused (we configure everything when bazel
+  # command is invoked).
+  upsalite_frontend_address: "https://source.cloud.google.com"
+}
diff --git a/tools/interop_matrix/client_matrix.py b/tools/interop_matrix/client_matrix.py
index 654dfd6..234c712 100644
--- a/tools/interop_matrix/client_matrix.py
+++ b/tools/interop_matrix/client_matrix.py
@@ -99,6 +99,8 @@
         ('v1.16.0', ReleaseInfo()),
         ('v1.17.1', ReleaseInfo()),
         ('v1.18.0', ReleaseInfo()),
+        ('v1.19.0', ReleaseInfo()),
+        ('v1.20.0', ReleaseInfo()),
     ]),
     'go':
     OrderedDict([
@@ -120,6 +122,8 @@
         ('v1.16.0', ReleaseInfo(runtime_subset=['go1.8'])),
         ('v1.17.0', ReleaseInfo(runtime_subset=['go1.11'])),
         ('v1.18.0', ReleaseInfo(runtime_subset=['go1.11'])),
+        ('v1.19.0', ReleaseInfo(runtime_subset=['go1.11'])),
+        ('v1.20.0', ReleaseInfo(runtime_subset=['go1.11'])),
     ]),
     'java':
     OrderedDict([
@@ -143,6 +147,7 @@
         ('v1.17.1', ReleaseInfo()),
         ('v1.18.0', ReleaseInfo()),
         ('v1.19.0', ReleaseInfo()),
+        ('v1.20.0', ReleaseInfo()),
     ]),
     'python':
     OrderedDict([
@@ -164,6 +169,8 @@
         ('v1.16.0', ReleaseInfo(testcases_file='python__v1.11.1')),
         ('v1.17.1', ReleaseInfo(testcases_file='python__v1.11.1')),
         ('v1.18.0', ReleaseInfo()),
+        ('v1.19.0', ReleaseInfo()),
+        ('v1.20.0', ReleaseInfo()),
     ]),
     'node':
     OrderedDict([
@@ -210,6 +217,9 @@
          ReleaseInfo(patch=[
              'tools/dockerfile/interoptest/grpc_interop_ruby/build_interop.sh',
          ])),
+        # TODO: https://github.com/grpc/grpc/issues/18262.
+        # If you are not encountering the error in above issue
+        # go ahead and upload the docker image for new releases.
     ]),
     'php':
     OrderedDict([
@@ -231,6 +241,8 @@
         ('v1.16.0', ReleaseInfo()),
         ('v1.17.1', ReleaseInfo()),
         ('v1.18.0', ReleaseInfo()),
+        # TODO:https://github.com/grpc/grpc/issues/18264
+        # Error in above issues needs to be resolved.
     ]),
     'csharp':
     OrderedDict([
@@ -257,6 +269,8 @@
         ('v1.15.0', ReleaseInfo(testcases_file='csharp__v1.3.9')),
         ('v1.16.0', ReleaseInfo(testcases_file='csharp__v1.3.9')),
         ('v1.17.1', ReleaseInfo(testcases_file='csharp__v1.3.9')),
-        ('v1.18.0', ReleaseInfo()),
+        ('v1.18.0', ReleaseInfo(testcases_file='csharp__v1.18.0')),
+        ('v1.19.0', ReleaseInfo(testcases_file='csharp__v1.18.0')),
+        ('v1.20.0', ReleaseInfo()),
     ]),
 }
diff --git a/tools/interop_matrix/testcases/csharp__master b/tools/interop_matrix/testcases/csharp__master
index 9f1cd05..e526b3c 100755
--- a/tools/interop_matrix/testcases/csharp__master
+++ b/tools/interop_matrix/testcases/csharp__master
@@ -1,7 +1,7 @@
 #!/bin/bash
 # DO NOT MODIFY
 # This file is generated by run_interop_tests.py/create_testcases.sh
-echo "Testing ${docker_image:=grpc_interop_csharp:71b05977-476b-4e57-9752-dd211c9e3741}"
+echo "Testing ${docker_image:=grpc_interop_csharp:9296f21a-f657-4bb0-82a7-24fc527abcbd}"
 docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
 docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
 docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
diff --git a/tools/interop_matrix/testcases/csharp__v1.18.0 b/tools/interop_matrix/testcases/csharp__v1.18.0
new file mode 100644
index 0000000..34444c7
--- /dev/null
+++ b/tools/interop_matrix/testcases/csharp__v1.18.0
@@ -0,0 +1,23 @@
+#!/bin/bash
+# DO NOT MODIFY
+# This file is generated by run_interop_tests.py/create_testcases.sh
+echo "Testing ${docker_image:=grpc_interop_csharp:71b05977-476b-4e57-9752-dd211c9e3741}"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 --net=host $docker_image bash -c "mono Grpc.IntegrationTesting.Client.exe --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
+
diff --git a/tools/interop_matrix/testcases/csharpcoreclr__master b/tools/interop_matrix/testcases/csharpcoreclr__master
index 3ca145e..e14ec04 100755
--- a/tools/interop_matrix/testcases/csharpcoreclr__master
+++ b/tools/interop_matrix/testcases/csharpcoreclr__master
@@ -1,22 +1,22 @@
 #!/bin/bash
 # DO NOT MODIFY
 # This file is generated by run_interop_tests.py/create_testcases.sh
-echo "Testing ${docker_image:=grpc_interop_csharpcoreclr:bae17a7e-5450-4781-8982-e82cb89db6dd}"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
-docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
+echo "Testing ${docker_image:=grpc_interop_csharpcoreclr:33395965-11d7-4b12-bcd6-a272d4015207}"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp2.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
diff --git a/tools/interop_matrix/testcases/csharpcoreclr__v1.18.0 b/tools/interop_matrix/testcases/csharpcoreclr__v1.18.0
new file mode 100755
index 0000000..3ca145e
--- /dev/null
+++ b/tools/interop_matrix/testcases/csharpcoreclr__v1.18.0
@@ -0,0 +1,22 @@
+#!/bin/bash
+# DO NOT MODIFY
+# This file is generated by run_interop_tests.py/create_testcases.sh
+echo "Testing ${docker_image:=grpc_interop_csharpcoreclr:bae17a7e-5450-4781-8982-e82cb89db6dd}"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=large_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_unary --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=ping_pong --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=empty_stream --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=client_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=server_streaming --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_begin --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=cancel_after_first_response --use_tls=true"
+docker run -i --rm=true -w /var/local/git/grpc/src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/netcoreapp1.1 --net=host $docker_image bash -c "dotnet exec Grpc.IntegrationTesting.Client.dll --server_host=grpc-test4.sandbox.googleapis.com --server_port=443 --test_case=timeout_on_sleeping_server --use_tls=true"
diff --git a/tools/release/release_notes.py b/tools/release/release_notes.py
new file mode 100644
index 0000000..46e0184
--- /dev/null
+++ b/tools/release/release_notes.py
@@ -0,0 +1,369 @@
+#Copyright 2019 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.
+"""Generate draft and release notes in Markdown from Github PRs.
+
+You'll need a github API token to avoid being rate-limited. See
+https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
+
+This script collects PRs using "git log X..Y" from local repo where X and Y are
+tags or release branch names of previous and current releases respectively.
+Typically, notes are generated before the release branch is labelled so Y is
+almost always the name of the release branch. X is the previous release branch
+if this is not a patch release. Otherwise, it is the previous release tag.
+For example, for release v1.17.0, X will be origin/v1.16.x and for release v1.17.3,
+X will be v1.17.2. In both cases Y will be origin/v1.17.x.
+
+"""
+
+from collections import defaultdict
+import base64
+import json
+
+content_header = """Draft Release Notes For {version}
+--
+Final release notes will be generated from the PR titles that have *"release notes:yes"* label. If you have any additional notes please add them below. These will be appended to auto generated release notes. Previous releases notes are [here](https://github.com/grpc/grpc/releases).
+
+**Also, look at the PRs listed below against your name.** Please apply the missing labels and make necessary corrections (like fixing the title) to the PR in Github. Final release notes will be generated just before the release on {date}.
+
+Add additional notes not in PRs
+--
+
+Core
+-
+
+
+C++
+-
+
+
+C#
+-
+
+
+Objective-C
+-
+
+
+PHP
+-
+
+
+Python
+-
+
+
+Ruby
+-
+
+
+"""
+
+rl_header = """This is the {version} release ([{name}](https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md)) of gRPC Core.
+
+Please see the notes for the previous releases here: https://github.com/grpc/grpc/releases. Please consult https://grpc.io/ for all information regarding this product.
+
+This release contains refinements, improvements, and bug fixes, with highlights listed below.
+
+
+"""
+
+HTML_URL = "https://github.com/grpc/grpc/pull/"
+API_URL = 'https://api.github.com/repos/grpc/grpc/pulls/'
+
+
+def get_commit_log(prevRelLabel, relBranch):
+    """Return the output of 'git log --pretty=online --merges prevRelLabel..relBranch' """
+
+    import subprocess
+    print("Running git log --pretty=oneline --merges " + prevRelLabel + ".." +
+          relBranch)
+    return subprocess.check_output([
+        "git", "log", "--pretty=oneline", "--merges",
+        "%s..%s" % (prevRelLabel, relBranch)
+    ])
+
+
+def get_pr_data(pr_num):
+    """Get the PR data from github. Return 'error' on exception"""
+
+    try:
+        from urllib2 import Request, urlopen, HTTPError
+    except ImportError:
+        import urllib
+        from urllib.request import Request, urlopen, HTTPError
+    url = API_URL + pr_num
+    req = Request(url)
+    req.add_header('Authorization', 'token %s' % TOKEN)
+    try:
+        f = urlopen(req)
+        response = json.loads(f.read().decode('utf-8'))
+        #print(response)
+    except HTTPError as e:
+        response = json.loads(e.fp.read().decode('utf-8'))
+        if 'message' in response:
+            print(response['message'])
+        response = "error"
+    return response
+
+
+def get_pr_titles(gitLogs):
+    import re
+    error_count = 0
+    match = b"Merge pull request #(\d+)"
+    prlist = re.findall(match, gitLogs, re.MULTILINE)
+    print("\nPRs matching 'Merge pull request #<num>':")
+    print(prlist)
+    print("\n")
+    langs_pr = defaultdict(list)
+    for pr_num in prlist:
+        pr_num = str(pr_num)
+        print("---------- getting data for PR " + pr_num)
+        pr = get_pr_data(pr_num)
+        if pr == "error":
+            print("\n***ERROR*** Error in getting data for PR " + pr_num + "\n")
+            error_count += 1
+            continue
+        rl_no_found = False
+        rl_yes_found = False
+        lang_found = False
+        for label in pr['labels']:
+            if label['name'] == 'release notes: yes':
+                rl_yes_found = True
+            elif label['name'] == 'release notes: no':
+                rl_no_found = True
+            elif label['name'].startswith('lang/'):
+                lang_found = True
+                lang = label['name'].split('/')[1].lower()
+                #lang = lang[0].upper() + lang[1:]
+        body = pr["title"]
+        if not body.endswith("."):
+            body = body + "."
+        if not pr["merged_by"]:
+            print("\n***ERROR***: No merge_by found for PR " + pr_num + "\n")
+            error_count += 1
+            continue
+
+        prline = "-  " + body + " ([#" + pr_num + "](" + HTML_URL + pr_num + "))"
+        detail = "- " + pr["merged_by"]["login"] + "@ " + prline
+        prline = prline.encode('ascii', 'ignore')
+        detail = detail.encode('ascii', 'ignore')
+        print(detail)
+        #if no RL label
+        if not rl_no_found and not rl_yes_found:
+            print("Release notes label missing for " + pr_num)
+            langs_pr["nolabel"].append(detail)
+        elif rl_yes_found and not lang_found:
+            print("Lang label missing for " + pr_num)
+            langs_pr["nolang"].append(detail)
+        elif rl_no_found:
+            print("'Release notes:no' found for " + pr_num)
+            langs_pr["notinrel"].append(detail)
+        elif rl_yes_found:
+            print("'Release notes:yes' found for " + pr_num + " with lang " +
+                  lang)
+            langs_pr["inrel"].append(detail)
+            langs_pr[lang].append(prline)
+
+    return langs_pr, error_count
+
+
+def write_draft(langs_pr, file, version, date):
+    file.write(content_header.format(version=version, date=date))
+    file.write("PRs with missing release notes label - please fix in Github\n")
+    file.write("---\n")
+    file.write("\n")
+    if langs_pr["nolabel"]:
+        langs_pr["nolabel"].sort()
+        file.write("\n".join(langs_pr["nolabel"]))
+    else:
+        file.write("- None")
+    file.write("\n")
+    file.write("\n")
+    file.write("PRs with missing lang label - please fix in Github\n")
+    file.write("---\n")
+    file.write("\n")
+    if langs_pr["nolang"]:
+        langs_pr["nolang"].sort()
+        file.write("\n".join(langs_pr["nolang"]))
+    else:
+        file.write("- None")
+    file.write("\n")
+    file.write("\n")
+    file.write(
+        "PRs going into release notes - please check title and fix in Github. Do not edit here.\n"
+    )
+    file.write("---\n")
+    file.write("\n")
+    if langs_pr["inrel"]:
+        langs_pr["inrel"].sort()
+        file.write("\n".join(langs_pr["inrel"]))
+    else:
+        file.write("- None")
+    file.write("\n")
+    file.write("\n")
+    file.write("PRs not going into release notes\n")
+    file.write("---\n")
+    file.write("\n")
+    if langs_pr["notinrel"]:
+        langs_pr["notinrel"].sort()
+        file.write("\n".join(langs_pr["notinrel"]))
+    else:
+        file.write("- None")
+    file.write("\n")
+    file.write("\n")
+
+
+def write_rel_notes(langs_pr, file, version, name):
+    file.write(rl_header.format(version=version, name=name))
+    if langs_pr["core"]:
+        file.write("Core\n---\n\n")
+        file.write("\n".join(langs_pr["core"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["c++"]:
+        file.write("C++\n---\n\n")
+        file.write("\n".join(langs_pr["c++"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["c#"]:
+        file.write("C#\n---\n\n")
+        file.write("\n".join(langs_pr["c#"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["go"]:
+        file.write("Go\n---\n\n")
+        file.write("\n".join(langs_pr["go"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["Java"]:
+        file.write("Java\n---\n\n")
+        file.write("\n".join(langs_pr["Java"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["node"]:
+        file.write("Node\n---\n\n")
+        file.write("\n".join(langs_pr["node"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["objc"]:
+        file.write("Objective-C\n---\n\n")
+        file.write("\n".join(langs_pr["objc"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["php"]:
+        file.write("PHP\n---\n\n")
+        file.write("\n".join(langs_pr["php"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["python"]:
+        file.write("Python\n---\n\n")
+        file.write("\n".join(langs_pr["python"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["ruby"]:
+        file.write("Ruby\n---\n\n")
+        file.write("\n".join(langs_pr["ruby"]))
+        file.write("\n")
+        file.write("\n")
+    if langs_pr["other"]:
+        file.write("Other\n---\n\n")
+        file.write("\n".join(langs_pr["other"]))
+        file.write("\n")
+        file.write("\n")
+
+
+def build_args_parser():
+    import argparse
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        'release_version', type=str, help='New release version e.g. 1.14.0')
+    parser.add_argument(
+        'release_name', type=str, help='New release name e.g. gladiolus')
+    parser.add_argument(
+        'release_date', type=str, help='Release date e.g. 7/30/18')
+    parser.add_argument(
+        'previous_release_label',
+        type=str,
+        help='Previous release branch/tag e.g. v1.13.x')
+    parser.add_argument(
+        'release_branch',
+        type=str,
+        help='Current release branch e.g. origin/v1.14.x')
+    parser.add_argument(
+        'draft_filename', type=str, help='Name of the draft file e.g. draft.md')
+    parser.add_argument(
+        'release_notes_filename',
+        type=str,
+        help='Name of the release notes file e.g. relnotes.md')
+    parser.add_argument(
+        '--token',
+        type=str,
+        default='',
+        help='GitHub API token to avoid being rate limited')
+    return parser
+
+
+def main():
+    import os
+    global TOKEN
+
+    parser = build_args_parser()
+    args = parser.parse_args()
+    version, name, date = args.release_version, args.release_name, args.release_date
+    start, end = args.previous_release_label, args.release_branch
+
+    TOKEN = args.token
+    if TOKEN == '':
+        try:
+            TOKEN = os.environ["GITHUB_TOKEN"]
+        except:
+            pass
+    if TOKEN == '':
+        print(
+            "Error: Github API token required. Either include param --token=<your github token> or set environment variable GITHUB_TOKEN to your github token"
+        )
+        return
+
+    langs_pr, error_count = get_pr_titles(get_commit_log(start, end))
+
+    draft_file, rel_file = args.draft_filename, args.release_notes_filename
+    filename = os.path.abspath(draft_file)
+    if os.path.exists(filename):
+        file = open(filename, 'r+')
+    else:
+        file = open(filename, 'w')
+
+    file.seek(0)
+    write_draft(langs_pr, file, version, date)
+    file.truncate()
+    file.close()
+    print("\nDraft notes written to " + filename)
+
+    filename = os.path.abspath(rel_file)
+    if os.path.exists(filename):
+        file = open(filename, 'r+')
+    else:
+        file = open(filename, 'w')
+
+    file.seek(0)
+    write_rel_notes(langs_pr, file, version, name)
+    file.truncate()
+    file.close()
+    print("\nRelease notes written to " + filename)
+    if error_count > 0:
+        print("\n\n*** Errors were encountered. See log. *********\n")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/remote_build/README.md b/tools/remote_build/README.md
index bb27dc7..2cd5f03 100644
--- a/tools/remote_build/README.md
+++ b/tools/remote_build/README.md
@@ -31,8 +31,8 @@
 
 Run on Windows MSVC:
 ```
-# local manual run only for C++ targets (RBE to be supported)
-bazel --bazelrc=tools/remote_build/windows.bazelrc test //test/cpp/...
+# RBE manual run only for c-core (must be run on a Windows host machine)
+bazel --bazelrc=tools/remote_build/windows.bazelrc build :all [--credentials_json=(path to service account credentials)]
 ```
 
 Available command line options can be found in
diff --git a/tools/remote_build/kokoro.bazelrc b/tools/remote_build/kokoro.bazelrc
index 2fbdd3c..064e94b 100644
--- a/tools/remote_build/kokoro.bazelrc
+++ b/tools/remote_build/kokoro.bazelrc
@@ -21,9 +21,6 @@
 build --tls_enabled=true
 
 build --auth_enabled=true
-# magic location where kokoro script puts the credentials
-build --auth_credentials=/tmpfs/src/keystore/4321_grpc-testing-service
-build --auth_scope=https://www.googleapis.com/auth/cloud-source-tools
 
 build --bes_backend=buildeventservice.googleapis.com
 build --bes_timeout=600s
diff --git a/tools/remote_build/rbe_common.bazelrc b/tools/remote_build/rbe_common.bazelrc
index 3438b18..69394fe 100644
--- a/tools/remote_build/rbe_common.bazelrc
+++ b/tools/remote_build/rbe_common.bazelrc
@@ -19,8 +19,8 @@
 
 startup --host_jvm_args=-Dbazel.DigestFunction=SHA256
 
-build --crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/ubuntu16_04_clang/1.1/bazel_0.20.0/default:toolchain
-build --extra_toolchains=//third_party/toolchains:cc-toolchain-clang-x86_64-default
+build --crosstool_top=@rbe_default//cc:toolchain
+build --extra_toolchains=@rbe_default//config:cc-toolchain
 # Use custom execution platforms defined in third_party/toolchains
 build --extra_execution_platforms=//third_party/toolchains:rbe_ubuntu1604,//third_party/toolchains:rbe_ubuntu1604_large
 build --host_platform=//third_party/toolchains:rbe_ubuntu1604
@@ -66,9 +66,9 @@
 # setting LD_LIBRARY_PATH is necessary
 # to avoid "libc++.so.1: cannot open shared object file"
 build:msan --action_env=LD_LIBRARY_PATH=/usr/local/lib
-build:msan --host_crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/ubuntu16_04_clang/1.1/bazel_0.20.0/default:toolchain
+build:msan --host_crosstool_top=@rbe_default//cc:toolchain
 # override the config-agnostic crosstool_top
-build:msan --crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/ubuntu16_04_clang/1.1/bazel_0.20.0/msan:toolchain
+build:msan --crosstool_top=@rbe_msan//cc:toolchain
 
 # thread sanitizer: most settings are already in %workspace%/.bazelrc
 # we only need a few additional ones that are Foundry specific
@@ -84,8 +84,4 @@
 # TODO(jtattermusch): use more reasonable test timeout
 build:ubsan --test_timeout=3600
 # override the config-agnostic crosstool_top
---crosstool_top=@com_github_bazelbuild_bazeltoolchains//configs/experimental/ubuntu16_04_clang/1.1/bazel_0.20.0/ubsan:toolchain
-# TODO(jtattermusch): remove this once Foundry adds the env to the docker image.
-# ubsan needs symbolizer to work properly, otherwise the suppression file doesn't work
-# and we get test failures.
-build:ubsan --action_env=UBSAN_SYMBOLIZER_PATH=/usr/local/bin/llvm-symbolizer
+build:ubsan --crosstool_top=@bazel_toolchains//configs/experimental/ubuntu16_04_clang/1.2/bazel_0.21.0/ubsan:toolchain
diff --git a/tools/remote_build/windows.bazelrc b/tools/remote_build/windows.bazelrc
index 7057537..492489f 100644
--- a/tools/remote_build/windows.bazelrc
+++ b/tools/remote_build/windows.bazelrc
@@ -1,3 +1,49 @@
-# TODO(yfen): Merge with rbe_common.bazelrc and enable Windows RBE
+startup --host_jvm_args=-Dbazel.DigestFunction=SHA256
+
+build --remote_cache=remotebuildexecution.googleapis.com
+build --remote_executor=remotebuildexecution.googleapis.com
+build --tls_enabled=true
+
+build --host_crosstool_top=//third_party/toolchains/bazel_0.23.2_rbe_windows:toolchain
+build --crosstool_top=//third_party/toolchains/bazel_0.23.2_rbe_windows:toolchain
+build --extra_toolchains=//third_party/toolchains/bazel_0.23.2_rbe_windows:cc-toolchain-x64_windows
+# Use custom execution platforms defined in third_party/toolchains
+build --extra_execution_platforms=//third_party/toolchains:rbe_windows
+build --host_platform=//third_party/toolchains:rbe_windows
+build --platforms=//third_party/toolchains:rbe_windows
+
+build --shell_executable=C:\\tools\\msys64\\usr\\bin\\bash.exe
+build --python_path=C:\\Python27\\python.exe
+
+build --spawn_strategy=remote
+build --strategy=Javac=remote
+build --strategy=Closure=remote
+build --genrule_strategy=remote
+build --remote_timeout=3600
+
+build --remote_instance_name=projects/grpc-testing/instances/grpc-windows-rbe-test
+
+build --verbose_failures=true
+
+build --experimental_strict_action_env=true
+build --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
+
+# don't use port server
+build --define GRPC_PORT_ISOLATED_RUNTIME=1
 build --test_tag_filters=-no_windows
 build --build_tag_filters=-no_windows
+
+# without verbose gRPC logs the test outputs are not very useful
+test --test_env=GRPC_VERBOSITY=debug
+
+# Set flags for uploading to BES in order to view results in the Bazel Build
+# Results UI.
+build --bes_backend="buildeventservice.googleapis.com"
+build --bes_timeout=60s
+build --bes_results_url="https://source.cloud.google.com/results/invocations/"
+build --project_id=grpc-testing
+
+build --jobs=30
+
+# print output for tests that fail (default is "summary")
+build --test_output=errors
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index 34d9e31..de8015a 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -3335,6 +3335,7 @@
       "gpr", 
       "grpc", 
       "grpc++", 
+      "grpc++_test_config", 
       "grpc++_test_util", 
       "grpc_test_util"
     ], 
@@ -3664,8 +3665,39 @@
   {
     "deps": [
       "gpr", 
+      "grpc_test_util_unsecure"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "global_config_env_test", 
+    "src": [
+      "test/core/gprpp/global_config_env_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "grpc_test_util_unsecure"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "global_config_test", 
+    "src": [
+      "test/core/gprpp/global_config_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
       "grpc", 
-      "grpc++"
+      "grpc++", 
+      "grpc++_test_config"
     ], 
     "headers": [
       "src/proto/grpc/testing/compiler_test.grpc.pb.h", 
@@ -3717,6 +3749,27 @@
   }, 
   {
     "deps": [
+      "gpr", 
+      "grpc", 
+      "grpc++", 
+      "grpc++_test", 
+      "grpc_test_util"
+    ], 
+    "headers": [
+      "test/core/gprpp/map_tester.h"
+    ], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "grpc_core_map_test", 
+    "src": [
+      "test/core/gprpp/map_test.cc", 
+      "test/core/gprpp/map_tester.h"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
       "grpc_plugin_support"
     ], 
     "headers": [], 
@@ -4133,6 +4186,24 @@
       "gpr", 
       "grpc", 
       "grpc++", 
+      "grpc++_test_util", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "message_allocator_end2end_test", 
+    "src": [
+      "test/cpp/end2end/message_allocator_end2end_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "grpc", 
+      "grpc++", 
       "grpc++_test_config"
     ], 
     "headers": [
@@ -4645,6 +4716,7 @@
       "gpr", 
       "grpc", 
       "grpc++", 
+      "grpc++_test_config", 
       "grpc++_test_util", 
       "grpc_test_util"
     ], 
@@ -4734,6 +4806,42 @@
     "headers": [], 
     "is_filegroup": false, 
     "language": "c++", 
+    "name": "service_config_end2end_test", 
+    "src": [
+      "test/cpp/end2end/service_config_end2end_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "grpc", 
+      "grpc++", 
+      "grpc++_test", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "service_config_test", 
+    "src": [
+      "test/core/client_channel/service_config_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "grpc", 
+      "grpc++", 
+      "grpc++_test_util", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
     "name": "shutdown_test", 
     "src": [
       "test/cpp/end2end/shutdown_test.cc"
@@ -5643,6 +5751,23 @@
     "headers": [], 
     "is_filegroup": false, 
     "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "src": [
+      "test/core/end2end/fixtures/h2_ssl_cred_reload.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "end2end_tests", 
+      "gpr", 
+      "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
     "name": "h2_ssl_proxy_test", 
     "src": [
       "test/core/end2end/fixtures/h2_ssl_proxy.cc"
@@ -5907,6 +6032,7 @@
   }, 
   {
     "deps": [
+      "dns_test_util", 
       "gpr", 
       "grpc++_test_config", 
       "grpc++_test_util_unsecure", 
@@ -5926,6 +6052,7 @@
   }, 
   {
     "deps": [
+      "dns_test_util", 
       "gpr", 
       "grpc", 
       "grpc++", 
@@ -6021,6 +6148,7 @@
   }, 
   {
     "deps": [
+      "dns_test_util", 
       "gpr", 
       "grpc", 
       "grpc++", 
@@ -6497,6 +6625,21 @@
     "type": "lib"
   }, 
   {
+    "deps": [], 
+    "headers": [
+      "test/cpp/naming/dns_test_util.h"
+    ], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "dns_test_util", 
+    "src": [
+      "test/cpp/naming/dns_test_util.cc", 
+      "test/cpp/naming/dns_test_util.h"
+    ], 
+    "third_party": false, 
+    "type": "lib"
+  }, 
+  {
     "deps": [
       "gpr", 
       "grpc", 
@@ -6585,6 +6728,7 @@
     "headers": [
       "include/grpc++/support/error_details.h", 
       "include/grpcpp/support/error_details.h", 
+      "include/grpcpp/support/error_details_impl.h", 
       "src/proto/grpc/status/status.grpc.pb.h", 
       "src/proto/grpc/status/status.pb.h", 
       "src/proto/grpc/status/status_mock.grpc.pb.h"
@@ -6595,6 +6739,7 @@
     "src": [
       "include/grpc++/support/error_details.h", 
       "include/grpcpp/support/error_details.h", 
+      "include/grpcpp/support/error_details_impl.h", 
       "src/cpp/util/error_details.cc"
     ], 
     "third_party": false, 
@@ -6629,6 +6774,7 @@
     "headers": [
       "include/grpc++/ext/proto_server_reflection_plugin.h", 
       "include/grpcpp/ext/proto_server_reflection_plugin.h", 
+      "include/grpcpp/ext/proto_server_reflection_plugin_impl.h", 
       "src/cpp/ext/proto_server_reflection.h"
     ], 
     "is_filegroup": false, 
@@ -6637,6 +6783,7 @@
     "src": [
       "include/grpc++/ext/proto_server_reflection_plugin.h", 
       "include/grpcpp/ext/proto_server_reflection_plugin.h", 
+      "include/grpcpp/ext/proto_server_reflection_plugin_impl.h", 
       "src/cpp/ext/proto_server_reflection.cc", 
       "src/cpp/ext/proto_server_reflection.h", 
       "src/cpp/ext/proto_server_reflection_plugin.cc"
@@ -7904,7 +8051,6 @@
     "name": "gpr_base", 
     "src": [
       "src/core/lib/gpr/alloc.cc", 
-      "src/core/lib/gpr/arena.cc", 
       "src/core/lib/gpr/atm.cc", 
       "src/core/lib/gpr/cpu_iphone.cc", 
       "src/core/lib/gpr/cpu_linux.cc", 
@@ -7937,7 +8083,9 @@
       "src/core/lib/gpr/tmpfile_posix.cc", 
       "src/core/lib/gpr/tmpfile_windows.cc", 
       "src/core/lib/gpr/wrap_memcpy.cc", 
+      "src/core/lib/gprpp/arena.cc", 
       "src/core/lib/gprpp/fork.cc", 
+      "src/core/lib/gprpp/global_config_env.cc", 
       "src/core/lib/gprpp/thd_posix.cc", 
       "src/core/lib/gprpp/thd_windows.cc", 
       "src/core/lib/profiling/basic_timers.cc", 
@@ -7985,11 +8133,18 @@
       "src/core/lib/gpr/tmpfile.h", 
       "src/core/lib/gpr/useful.h", 
       "src/core/lib/gprpp/abstract.h", 
+      "src/core/lib/gprpp/arena.h", 
       "src/core/lib/gprpp/atomic.h", 
       "src/core/lib/gprpp/fork.h", 
+      "src/core/lib/gprpp/global_config.h", 
+      "src/core/lib/gprpp/global_config_custom.h", 
+      "src/core/lib/gprpp/global_config_env.h", 
+      "src/core/lib/gprpp/global_config_generic.h", 
       "src/core/lib/gprpp/manual_constructor.h", 
+      "src/core/lib/gprpp/map.h", 
       "src/core/lib/gprpp/memory.h", 
-      "src/core/lib/gprpp/mutex_lock.h", 
+      "src/core/lib/gprpp/pair.h", 
+      "src/core/lib/gprpp/sync.h", 
       "src/core/lib/gprpp/thd.h", 
       "src/core/lib/profiling/timers.h"
     ], 
@@ -8031,11 +8186,18 @@
       "src/core/lib/gpr/tmpfile.h", 
       "src/core/lib/gpr/useful.h", 
       "src/core/lib/gprpp/abstract.h", 
+      "src/core/lib/gprpp/arena.h", 
       "src/core/lib/gprpp/atomic.h", 
       "src/core/lib/gprpp/fork.h", 
+      "src/core/lib/gprpp/global_config.h", 
+      "src/core/lib/gprpp/global_config_custom.h", 
+      "src/core/lib/gprpp/global_config_env.h", 
+      "src/core/lib/gprpp/global_config_generic.h", 
       "src/core/lib/gprpp/manual_constructor.h", 
+      "src/core/lib/gprpp/map.h", 
       "src/core/lib/gprpp/memory.h", 
-      "src/core/lib/gprpp/mutex_lock.h", 
+      "src/core/lib/gprpp/pair.h", 
+      "src/core/lib/gprpp/sync.h", 
       "src/core/lib/gprpp/thd.h", 
       "src/core/lib/profiling/timers.h"
     ], 
@@ -8137,6 +8299,7 @@
       "src/core/lib/channel/handshaker_registry.cc", 
       "src/core/lib/channel/status_util.cc", 
       "src/core/lib/compression/compression.cc", 
+      "src/core/lib/compression/compression_args.cc", 
       "src/core/lib/compression/compression_internal.cc", 
       "src/core/lib/compression/message_compress.cc", 
       "src/core/lib/compression/stream_compression.cc", 
@@ -8149,12 +8312,15 @@
       "src/core/lib/http/parser.cc", 
       "src/core/lib/iomgr/buffer_list.cc", 
       "src/core/lib/iomgr/call_combiner.cc", 
+      "src/core/lib/iomgr/cfstream_handle.cc", 
       "src/core/lib/iomgr/combiner.cc", 
       "src/core/lib/iomgr/endpoint.cc", 
+      "src/core/lib/iomgr/endpoint_cfstream.cc", 
       "src/core/lib/iomgr/endpoint_pair_posix.cc", 
       "src/core/lib/iomgr/endpoint_pair_uv.cc", 
       "src/core/lib/iomgr/endpoint_pair_windows.cc", 
       "src/core/lib/iomgr/error.cc", 
+      "src/core/lib/iomgr/error_cfstream.cc", 
       "src/core/lib/iomgr/ev_epoll1_linux.cc", 
       "src/core/lib/iomgr/ev_epollex_linux.cc", 
       "src/core/lib/iomgr/ev_poll_posix.cc", 
@@ -8175,6 +8341,7 @@
       "src/core/lib/iomgr/iomgr_custom.cc", 
       "src/core/lib/iomgr/iomgr_internal.cc", 
       "src/core/lib/iomgr/iomgr_posix.cc", 
+      "src/core/lib/iomgr/iomgr_posix_cfstream.cc", 
       "src/core/lib/iomgr/iomgr_uv.cc", 
       "src/core/lib/iomgr/iomgr_windows.cc", 
       "src/core/lib/iomgr/is_epollexclusive_available.cc", 
@@ -8203,6 +8370,7 @@
       "src/core/lib/iomgr/socket_utils_windows.cc", 
       "src/core/lib/iomgr/socket_windows.cc", 
       "src/core/lib/iomgr/tcp_client.cc", 
+      "src/core/lib/iomgr/tcp_client_cfstream.cc", 
       "src/core/lib/iomgr/tcp_client_custom.cc", 
       "src/core/lib/iomgr/tcp_client_posix.cc", 
       "src/core/lib/iomgr/tcp_client_windows.cc", 
@@ -8311,6 +8479,7 @@
       "src/core/lib/channel/handshaker_registry.h", 
       "src/core/lib/channel/status_util.h", 
       "src/core/lib/compression/algorithm_metadata.h", 
+      "src/core/lib/compression/compression_args.h", 
       "src/core/lib/compression/compression_internal.h", 
       "src/core/lib/compression/message_compress.h", 
       "src/core/lib/compression/stream_compression.h", 
@@ -8330,12 +8499,15 @@
       "src/core/lib/iomgr/block_annotate.h", 
       "src/core/lib/iomgr/buffer_list.h", 
       "src/core/lib/iomgr/call_combiner.h", 
+      "src/core/lib/iomgr/cfstream_handle.h", 
       "src/core/lib/iomgr/closure.h", 
       "src/core/lib/iomgr/combiner.h", 
       "src/core/lib/iomgr/dynamic_annotations.h", 
       "src/core/lib/iomgr/endpoint.h", 
+      "src/core/lib/iomgr/endpoint_cfstream.h", 
       "src/core/lib/iomgr/endpoint_pair.h", 
       "src/core/lib/iomgr/error.h", 
+      "src/core/lib/iomgr/error_cfstream.h", 
       "src/core/lib/iomgr/error_internal.h", 
       "src/core/lib/iomgr/ev_epoll1_linux.h", 
       "src/core/lib/iomgr/ev_epollex_linux.h", 
@@ -8463,6 +8635,7 @@
       "src/core/lib/channel/handshaker_registry.h", 
       "src/core/lib/channel/status_util.h", 
       "src/core/lib/compression/algorithm_metadata.h", 
+      "src/core/lib/compression/compression_args.h", 
       "src/core/lib/compression/compression_internal.h", 
       "src/core/lib/compression/message_compress.h", 
       "src/core/lib/compression/stream_compression.h", 
@@ -8482,12 +8655,15 @@
       "src/core/lib/iomgr/block_annotate.h", 
       "src/core/lib/iomgr/buffer_list.h", 
       "src/core/lib/iomgr/call_combiner.h", 
+      "src/core/lib/iomgr/cfstream_handle.h", 
       "src/core/lib/iomgr/closure.h", 
       "src/core/lib/iomgr/combiner.h", 
       "src/core/lib/iomgr/dynamic_annotations.h", 
       "src/core/lib/iomgr/endpoint.h", 
+      "src/core/lib/iomgr/endpoint_cfstream.h", 
       "src/core/lib/iomgr/endpoint_pair.h", 
       "src/core/lib/iomgr/error.h", 
+      "src/core/lib/iomgr/error_cfstream.h", 
       "src/core/lib/iomgr/error_internal.h", 
       "src/core/lib/iomgr/ev_epoll1_linux.h", 
       "src/core/lib/iomgr/ev_epollex_linux.h", 
@@ -8590,33 +8766,6 @@
   {
     "deps": [
       "gpr", 
-      "gpr_base_headers", 
-      "grpc_base_headers"
-    ], 
-    "headers": [
-      "src/core/lib/iomgr/cfstream_handle.h", 
-      "src/core/lib/iomgr/endpoint_cfstream.h", 
-      "src/core/lib/iomgr/error_cfstream.h"
-    ], 
-    "is_filegroup": true, 
-    "language": "c", 
-    "name": "grpc_cfstream", 
-    "src": [
-      "src/core/lib/iomgr/cfstream_handle.cc", 
-      "src/core/lib/iomgr/cfstream_handle.h", 
-      "src/core/lib/iomgr/endpoint_cfstream.cc", 
-      "src/core/lib/iomgr/endpoint_cfstream.h", 
-      "src/core/lib/iomgr/error_cfstream.cc", 
-      "src/core/lib/iomgr/error_cfstream.h", 
-      "src/core/lib/iomgr/iomgr_posix_cfstream.cc", 
-      "src/core/lib/iomgr/tcp_client_cfstream.cc"
-    ], 
-    "third_party": false, 
-    "type": "filegroup"
-  }, 
-  {
-    "deps": [
-      "gpr", 
       "grpc_base"
     ], 
     "headers": [
@@ -9024,11 +9173,13 @@
     "deps": [
       "gpr", 
       "grpc_base", 
-      "grpc_client_channel"
+      "grpc_client_channel", 
+      "grpc_resolver_dns_selection"
     ], 
     "headers": [
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h", 
-      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h", 
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h"
     ], 
     "is_filegroup": true, 
     "language": "c", 
@@ -9037,11 +9188,15 @@
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h", 
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc", 
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc", 
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.cc", 
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv_windows.h", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc"
     ], 
@@ -9052,7 +9207,8 @@
     "deps": [
       "gpr", 
       "grpc_base", 
-      "grpc_client_channel"
+      "grpc_client_channel", 
+      "grpc_resolver_dns_selection"
     ], 
     "headers": [], 
     "is_filegroup": true, 
@@ -9067,6 +9223,24 @@
   {
     "deps": [
       "gpr", 
+      "grpc_base"
+    ], 
+    "headers": [
+      "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
+    ], 
+    "is_filegroup": true, 
+    "language": "c", 
+    "name": "grpc_resolver_dns_selection", 
+    "src": [
+      "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc", 
+      "src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h"
+    ], 
+    "third_party": false, 
+    "type": "filegroup"
+  }, 
+  {
+    "deps": [
+      "gpr", 
       "grpc_base", 
       "grpc_client_channel"
     ], 
@@ -9813,6 +9987,7 @@
   }, 
   {
     "deps": [
+      "grpc++_internal_hdrs_only", 
       "grpc_codegen"
     ], 
     "headers": [
@@ -9861,6 +10036,7 @@
       "include/grpcpp/impl/codegen/client_interceptor.h", 
       "include/grpcpp/impl/codegen/client_unary_call.h", 
       "include/grpcpp/impl/codegen/completion_queue.h", 
+      "include/grpcpp/impl/codegen/completion_queue_impl.h", 
       "include/grpcpp/impl/codegen/completion_queue_tag.h", 
       "include/grpcpp/impl/codegen/config.h", 
       "include/grpcpp/impl/codegen/core_codegen_interface.h", 
@@ -9869,6 +10045,7 @@
       "include/grpcpp/impl/codegen/intercepted_channel.h", 
       "include/grpcpp/impl/codegen/interceptor.h", 
       "include/grpcpp/impl/codegen/interceptor_common.h", 
+      "include/grpcpp/impl/codegen/message_allocator.h", 
       "include/grpcpp/impl/codegen/metadata_map.h", 
       "include/grpcpp/impl/codegen/method_handler_impl.h", 
       "include/grpcpp/impl/codegen/rpc_method.h", 
@@ -9937,6 +10114,7 @@
       "include/grpcpp/impl/codegen/client_interceptor.h", 
       "include/grpcpp/impl/codegen/client_unary_call.h", 
       "include/grpcpp/impl/codegen/completion_queue.h", 
+      "include/grpcpp/impl/codegen/completion_queue_impl.h", 
       "include/grpcpp/impl/codegen/completion_queue_tag.h", 
       "include/grpcpp/impl/codegen/config.h", 
       "include/grpcpp/impl/codegen/core_codegen_interface.h", 
@@ -9945,6 +10123,7 @@
       "include/grpcpp/impl/codegen/intercepted_channel.h", 
       "include/grpcpp/impl/codegen/interceptor.h", 
       "include/grpcpp/impl/codegen/interceptor_common.h", 
+      "include/grpcpp/impl/codegen/message_allocator.h", 
       "include/grpcpp/impl/codegen/metadata_map.h", 
       "include/grpcpp/impl/codegen/method_handler_impl.h", 
       "include/grpcpp/impl/codegen/rpc_method.h", 
@@ -10009,6 +10188,7 @@
       "gpr", 
       "gpr_base_headers", 
       "grpc++_codegen_base", 
+      "grpc++_internal_hdrs_only", 
       "grpc_base_headers", 
       "grpc_transport_inproc_headers", 
       "health_proto", 
@@ -10063,16 +10243,20 @@
       "include/grpcpp/alarm.h", 
       "include/grpcpp/alarm_impl.h", 
       "include/grpcpp/channel.h", 
+      "include/grpcpp/channel_impl.h", 
       "include/grpcpp/client_context.h", 
       "include/grpcpp/completion_queue.h", 
       "include/grpcpp/create_channel.h", 
+      "include/grpcpp/create_channel_impl.h", 
       "include/grpcpp/create_channel_posix.h", 
       "include/grpcpp/create_channel_posix_impl.h", 
       "include/grpcpp/ext/health_check_service_server_builder_option.h", 
       "include/grpcpp/generic/async_generic_service.h", 
       "include/grpcpp/generic/generic_stub.h", 
+      "include/grpcpp/generic/generic_stub_impl.h", 
       "include/grpcpp/grpcpp.h", 
       "include/grpcpp/health_check_service_interface.h", 
+      "include/grpcpp/health_check_service_interface_impl.h", 
       "include/grpcpp/impl/call.h", 
       "include/grpcpp/impl/channel_argument_option.h", 
       "include/grpcpp/impl/client_unary_call.h", 
@@ -10086,25 +10270,34 @@
       "include/grpcpp/impl/server_builder_option_impl.h", 
       "include/grpcpp/impl/server_builder_plugin.h", 
       "include/grpcpp/impl/server_initializer.h", 
+      "include/grpcpp/impl/server_initializer_impl.h", 
       "include/grpcpp/impl/service_type.h", 
       "include/grpcpp/resource_quota.h", 
+      "include/grpcpp/resource_quota_impl.h", 
       "include/grpcpp/security/auth_context.h", 
       "include/grpcpp/security/auth_metadata_processor.h", 
+      "include/grpcpp/security/auth_metadata_processor_impl.h", 
       "include/grpcpp/security/credentials.h", 
+      "include/grpcpp/security/credentials_impl.h", 
       "include/grpcpp/security/server_credentials.h", 
+      "include/grpcpp/security/server_credentials_impl.h", 
       "include/grpcpp/server.h", 
       "include/grpcpp/server_builder.h", 
+      "include/grpcpp/server_builder_impl.h", 
       "include/grpcpp/server_context.h", 
+      "include/grpcpp/server_impl.h", 
       "include/grpcpp/server_posix.h", 
       "include/grpcpp/server_posix_impl.h", 
       "include/grpcpp/support/async_stream.h", 
       "include/grpcpp/support/async_unary_call.h", 
       "include/grpcpp/support/byte_buffer.h", 
       "include/grpcpp/support/channel_arguments.h", 
+      "include/grpcpp/support/channel_arguments_impl.h", 
       "include/grpcpp/support/client_callback.h", 
       "include/grpcpp/support/client_interceptor.h", 
       "include/grpcpp/support/config.h", 
       "include/grpcpp/support/interceptor.h", 
+      "include/grpcpp/support/message_allocator.h", 
       "include/grpcpp/support/proto_buffer_reader.h", 
       "include/grpcpp/support/proto_buffer_writer.h", 
       "include/grpcpp/support/server_callback.h", 
@@ -10175,16 +10368,20 @@
       "include/grpcpp/alarm.h", 
       "include/grpcpp/alarm_impl.h", 
       "include/grpcpp/channel.h", 
+      "include/grpcpp/channel_impl.h", 
       "include/grpcpp/client_context.h", 
       "include/grpcpp/completion_queue.h", 
       "include/grpcpp/create_channel.h", 
+      "include/grpcpp/create_channel_impl.h", 
       "include/grpcpp/create_channel_posix.h", 
       "include/grpcpp/create_channel_posix_impl.h", 
       "include/grpcpp/ext/health_check_service_server_builder_option.h", 
       "include/grpcpp/generic/async_generic_service.h", 
       "include/grpcpp/generic/generic_stub.h", 
+      "include/grpcpp/generic/generic_stub_impl.h", 
       "include/grpcpp/grpcpp.h", 
       "include/grpcpp/health_check_service_interface.h", 
+      "include/grpcpp/health_check_service_interface_impl.h", 
       "include/grpcpp/impl/call.h", 
       "include/grpcpp/impl/channel_argument_option.h", 
       "include/grpcpp/impl/client_unary_call.h", 
@@ -10198,25 +10395,34 @@
       "include/grpcpp/impl/server_builder_option_impl.h", 
       "include/grpcpp/impl/server_builder_plugin.h", 
       "include/grpcpp/impl/server_initializer.h", 
+      "include/grpcpp/impl/server_initializer_impl.h", 
       "include/grpcpp/impl/service_type.h", 
       "include/grpcpp/resource_quota.h", 
+      "include/grpcpp/resource_quota_impl.h", 
       "include/grpcpp/security/auth_context.h", 
       "include/grpcpp/security/auth_metadata_processor.h", 
+      "include/grpcpp/security/auth_metadata_processor_impl.h", 
       "include/grpcpp/security/credentials.h", 
+      "include/grpcpp/security/credentials_impl.h", 
       "include/grpcpp/security/server_credentials.h", 
+      "include/grpcpp/security/server_credentials_impl.h", 
       "include/grpcpp/server.h", 
       "include/grpcpp/server_builder.h", 
+      "include/grpcpp/server_builder_impl.h", 
       "include/grpcpp/server_context.h", 
+      "include/grpcpp/server_impl.h", 
       "include/grpcpp/server_posix.h", 
       "include/grpcpp/server_posix_impl.h", 
       "include/grpcpp/support/async_stream.h", 
       "include/grpcpp/support/async_unary_call.h", 
       "include/grpcpp/support/byte_buffer.h", 
       "include/grpcpp/support/channel_arguments.h", 
+      "include/grpcpp/support/channel_arguments_impl.h", 
       "include/grpcpp/support/client_callback.h", 
       "include/grpcpp/support/client_interceptor.h", 
       "include/grpcpp/support/config.h", 
       "include/grpcpp/support/interceptor.h", 
+      "include/grpcpp/support/message_allocator.h", 
       "include/grpcpp/support/proto_buffer_reader.h", 
       "include/grpcpp/support/proto_buffer_writer.h", 
       "include/grpcpp/support/server_callback.h", 
@@ -10290,6 +10496,20 @@
   {
     "deps": [], 
     "headers": [
+      "include/grpcpp/impl/codegen/sync.h"
+    ], 
+    "is_filegroup": true, 
+    "language": "c++", 
+    "name": "grpc++_internal_hdrs_only", 
+    "src": [
+      "include/grpcpp/impl/codegen/sync.h"
+    ], 
+    "third_party": false, 
+    "type": "filegroup"
+  }, 
+  {
+    "deps": [], 
+    "headers": [
       "src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.h", 
       "src/proto/grpc/reflection/v1alpha/reflection.pb.h", 
       "src/proto/grpc/reflection/v1alpha/reflection_mock.grpc.pb.h"
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index cf0e574..c156c83 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -4500,6 +4500,54 @@
     "uses_polling": true
   }, 
   {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c++", 
+    "name": "global_config_env_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c++", 
+    "name": "global_config_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "uses_polling": false
+  }, 
+  {
     "args": [
       "--generated_file_path=gens/src/proto/grpc/testing/"
     ], 
@@ -4564,6 +4612,30 @@
     "flaky": false, 
     "gtest": true, 
     "language": "c++", 
+    "name": "grpc_core_map_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "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": "grpc_linux_system_roots_test", 
     "platforms": [
       "linux", 
@@ -4842,6 +4914,30 @@
       "posix", 
       "windows"
     ], 
+    "cpu_cost": 0.5, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": true, 
+    "language": "c++", 
+    "name": "message_allocator_end2end_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "uses_polling": true
+  }, 
+  {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
     "cpu_cost": 1.0, 
     "exclude_configs": [], 
     "exclude_iomgrs": [], 
@@ -5364,6 +5460,54 @@
     "flaky": false, 
     "gtest": true, 
     "language": "c++", 
+    "name": "service_config_end2end_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "uses_polling": true
+  }, 
+  {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": true, 
+    "language": "c++", 
+    "name": "service_config_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "uses_polling": true
+  }, 
+  {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": true, 
+    "language": "c++", 
     "name": "shutdown_test", 
     "platforms": [
       "linux", 
@@ -36826,6 +36970,1781 @@
     "ci_platforms": [
       "windows", 
       "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "bad_hostname"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "bad_ping"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "binary_metadata"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "call_creds"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "call_host_override"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "cancel_after_accept"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "cancel_after_client_done"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "cancel_after_invoke"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "cancel_after_round_trip"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "cancel_before_invoke"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "cancel_in_a_vacuum"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "cancel_with_status"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "channelz"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "compressed_payload"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "connectivity"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "default_host"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "disappearing_server"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": true, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "empty_batch"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "filter_call_init_fails"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "filter_causes_close"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "filter_context"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "filter_latency"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "filter_status_code"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "graceful_server_shutdown"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "high_initial_seqno"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "hpack_size"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "idempotent_request"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "invoke_large_request"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "keepalive_timeout"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "large_metadata"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "max_concurrent_streams"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "max_connection_age"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "max_connection_idle"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "max_message_length"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "negative_deadline"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "no_error_on_hotpath"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "no_logging"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "no_op"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "payload"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "ping"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "ping_pong_streaming"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "registered_call"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "request_with_flags"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "request_with_payload"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "resource_quota_server"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_cancellation"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_disabled"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_exceeds_buffer_size_in_initial_batch"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_exceeds_buffer_size_in_subsequent_batch"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_non_retriable_status"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_non_retriable_status_before_recv_trailing_metadata_started"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_recv_initial_metadata"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_recv_message"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_server_pushback_delay"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_server_pushback_disabled"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_streaming"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_streaming_after_commit"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_streaming_succeeds_before_replay_finished"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_throttled"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "retry_too_many_attempts"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "server_finishes_request"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "shutdown_finishes_calls"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "shutdown_finishes_tags"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "simple_cacheable_request"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "simple_delayed_request"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "simple_metadata"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "simple_request"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "stream_compression_compressed_payload"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "stream_compression_payload"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "stream_compression_ping_pong_streaming"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "streaming_error_response"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "trailing_metadata"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "workaround_cronet_compression"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "write_buffering"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "write_buffering_at_end"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "h2_ssl_cred_reload_test", 
+    "platforms": [
+      "windows", 
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [
+      "authority_not_supported"
+    ], 
+    "ci_platforms": [
+      "windows", 
+      "linux", 
       "posix"
     ], 
     "cpu_cost": 1.0, 
diff --git a/tools/run_tests/python_utils/upload_rbe_results.py b/tools/run_tests/python_utils/upload_rbe_results.py
index bda567e..e650479 100755
--- a/tools/run_tests/python_utils/upload_rbe_results.py
+++ b/tools/run_tests/python_utils/upload_rbe_results.py
@@ -146,6 +146,8 @@
     invocation_id = args.invocation_id or _get_invocation_id()
     resultstore_actions = _get_resultstore_data(api_key, invocation_id)
 
+    # google.devtools.resultstore.v2.Action schema:
+    # https://github.com/googleapis/googleapis/blob/master/google/devtools/resultstore/v2/action.proto
     bq_rows = []
     for index, action in enumerate(resultstore_actions):
         # Filter out non-test related data, such as build results.
@@ -187,6 +189,8 @@
             }
         elif 'testSuite' not in action['testAction']:
             continue
+        elif 'tests' not in action['testAction']['testSuite']:
+            continue
         else:
             test_cases = action['testAction']['testSuite']['tests'][0][
                 'testSuite']['tests']
diff --git a/tools/run_tests/run_microbenchmark.py b/tools/run_tests/run_microbenchmark.py
index 4e4d05c..a7fde30 100755
--- a/tools/run_tests/run_microbenchmark.py
+++ b/tools/run_tests/run_microbenchmark.py
@@ -96,7 +96,7 @@
                     '--benchmark_filter=^%s$' % line,
                     '--benchmark_min_time=0.05'
                 ],
-                environ={'LATENCY_TRACE': '%s.trace' % fnize(line)},
+                environ={'GRPC_LATENCY_TRACE': '%s.trace' % fnize(line)},
                 shortname='profile-%s' % fnize(line)))
         profile_analysis.append(
             jobset.JobSpec(
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 1c4d20e..a7a9dbd 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -1064,33 +1064,6 @@
             self.config.job_spec(
                 ['src/objective-c/tests/build_one_example.sh'],
                 timeout_seconds=10 * 60,
-                shortname='objc-build-example-helloworld',
-                cpu_cost=1e6,
-                environ={
-                    'SCHEME': 'HelloWorld',
-                    'EXAMPLE_PATH': 'examples/objective-c/helloworld'
-                }),
-            self.config.job_spec(
-                ['src/objective-c/tests/build_one_example.sh'],
-                timeout_seconds=10 * 60,
-                shortname='objc-build-example-routeguide',
-                cpu_cost=1e6,
-                environ={
-                    'SCHEME': 'RouteGuideClient',
-                    'EXAMPLE_PATH': 'examples/objective-c/route_guide'
-                }),
-            self.config.job_spec(
-                ['src/objective-c/tests/build_one_example.sh'],
-                timeout_seconds=10 * 60,
-                shortname='objc-build-example-authsample',
-                cpu_cost=1e6,
-                environ={
-                    'SCHEME': 'AuthSample',
-                    'EXAMPLE_PATH': 'examples/objective-c/auth_sample'
-                }),
-            self.config.job_spec(
-                ['src/objective-c/tests/build_one_example.sh'],
-                timeout_seconds=10 * 60,
                 shortname='objc-build-example-sample',
                 cpu_cost=1e6,
                 environ={
diff --git a/tools/run_tests/run_tests_matrix.py b/tools/run_tests/run_tests_matrix.py
index d93add0..785dff3 100755
--- a/tools/run_tests/run_tests_matrix.py
+++ b/tools/run_tests/run_tests_matrix.py
@@ -197,6 +197,7 @@
         inner_jobs=inner_jobs,
         timeout_seconds=_CPP_RUNTESTS_TIMEOUT)
 
+    # C# tests on .NET desktop/mono
     test_jobs += _generate_jobs(
         languages=['csharp'],
         configs=['dbg', 'opt'],
@@ -204,6 +205,16 @@
         labels=['basictests', 'multilang'],
         extra_args=extra_args,
         inner_jobs=inner_jobs)
+    # C# tests on .NET core
+    test_jobs += _generate_jobs(
+        languages=['csharp'],
+        configs=['dbg', 'opt'],
+        platforms=['linux', 'macos', 'windows'],
+        arch='default',
+        compiler='coreclr',
+        labels=['basictests', 'multilang'],
+        extra_args=extra_args,
+        inner_jobs=inner_jobs)
 
     test_jobs += _generate_jobs(
         languages=['python'],
@@ -393,16 +404,6 @@
         inner_jobs=inner_jobs)
 
     test_jobs += _generate_jobs(
-        languages=['csharp'],
-        configs=['dbg'],
-        platforms=['linux'],
-        arch='default',
-        compiler='coreclr',
-        labels=['portability', 'multilang'],
-        extra_args=extra_args,
-        inner_jobs=inner_jobs)
-
-    test_jobs += _generate_jobs(
         languages=['c'],
         configs=['dbg'],
         platforms=['linux'],
diff --git a/tools/run_tests/sanity/check_bazel_workspace.py b/tools/run_tests/sanity/check_bazel_workspace.py
index 3601634..2017f58 100755
--- a/tools/run_tests/sanity/check_bazel_workspace.py
+++ b/tools/run_tests/sanity/check_bazel_workspace.py
@@ -35,7 +35,7 @@
 }
 
 _BAZEL_SKYLIB_DEP_NAME = 'bazel_skylib'
-_BAZEL_TOOLCHAINS_DEP_NAME = 'com_github_bazelbuild_bazeltoolchains'
+_BAZEL_TOOLCHAINS_DEP_NAME = 'bazel_toolchains'
 _TWISTED_TWISTED_DEP_NAME = 'com_github_twisted_twisted'
 _YAML_PYYAML_DEP_NAME = 'com_github_yaml_pyyaml'
 _TWISTED_INCREMENTAL_DEP_NAME = 'com_github_twisted_incremental'
diff --git a/tools/run_tests/sanity/core_banned_functions.py b/tools/run_tests/sanity/core_banned_functions.py
index 549ae14..ce9ff0d 100755
--- a/tools/run_tests/sanity/core_banned_functions.py
+++ b/tools/run_tests/sanity/core_banned_functions.py
@@ -45,10 +45,6 @@
     'grpc_closure_sched(': ['src/core/lib/iomgr/closure.cc'],
     'grpc_closure_run(': ['src/core/lib/iomgr/closure.cc'],
     'grpc_closure_list_sched(': ['src/core/lib/iomgr/closure.cc'],
-    'gpr_getenv_silent(': [
-        'src/core/lib/gpr/log.cc', 'src/core/lib/gpr/env_linux.cc',
-        'src/core/lib/gpr/env_posix.cc', 'src/core/lib/gpr/env_windows.cc'
-    ],
 }
 
 errors = 0