Merge remote-tracking branch 'upstream/master' into server_connection_timeout
diff --git a/BUILD b/BUILD
index 4e04b8c..25daba8 100644
--- a/BUILD
+++ b/BUILD
@@ -480,6 +480,7 @@
     ],
     hdrs = [
         "src/core/lib/profiling/timers.h",
+        "src/core/lib/support/abstract.h",
         "src/core/lib/support/arena.h",
         "src/core/lib/support/atomic.h",
         "src/core/lib/support/atomic_with_atm.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bae6bb2..387f29c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -430,6 +430,7 @@
 add_dependencies(buildtests_c gpr_histogram_test)
 add_dependencies(buildtests_c gpr_host_port_test)
 add_dependencies(buildtests_c gpr_log_test)
+add_dependencies(buildtests_c gpr_manual_constructor_test)
 add_dependencies(buildtests_c gpr_mpscq_test)
 add_dependencies(buildtests_c gpr_spinlock_test)
 add_dependencies(buildtests_c gpr_stack_lockfree_test)
@@ -6391,6 +6392,34 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(gpr_manual_constructor_test
+  test/core/support/manual_constructor_test.cc
+)
+
+
+target_include_directories(gpr_manual_constructor_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${BORINGSSL_ROOT_DIR}/include
+  PRIVATE ${PROTOBUF_ROOT_DIR}/src
+  PRIVATE ${BENCHMARK_ROOT_DIR}/include
+  PRIVATE ${ZLIB_ROOT_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib
+  PRIVATE ${CARES_INCLUDE_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/cares/cares
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/gflags/include
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/abseil-cpp
+)
+
+target_link_libraries(gpr_manual_constructor_test
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  gpr_test_util
+  gpr
+)
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(gpr_mpscq_test
   test/core/support/mpscq_test.cc
 )
diff --git a/Makefile b/Makefile
index 3106475..412ff24 100644
--- a/Makefile
+++ b/Makefile
@@ -114,7 +114,7 @@
 CXX_msan = clang++
 LD_msan = clang++
 LDXX_msan = clang++
-CPPFLAGS_msan = -O0 -fsanitize-coverage=edge -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -DGTEST_HAS_TR1_TUPLE=0 -DGTEST_USE_OWN_TR1_TUPLE=1 -Wno-unused-command-line-argument -fPIE -pie -DGPR_NO_DIRECT_SYSCALLS
+CPPFLAGS_msan = -O0 -fsanitize-coverage=edge -fsanitize=memory -fsanitize-memory-track-origins -fsanitize-memory-use-after-dtor -fno-omit-frame-pointer -DGTEST_HAS_TR1_TUPLE=0 -DGTEST_USE_OWN_TR1_TUPLE=1 -Wno-unused-command-line-argument -fPIE -pie -DGPR_NO_DIRECT_SYSCALLS
 LDFLAGS_msan = -fsanitize=memory -DGTEST_HAS_TR1_TUPLE=0 -DGTEST_USE_OWN_TR1_TUPLE=1 -fPIE -pie $(if $(JENKINS_BUILD),-Wl$(comma)-Ttext-segment=0x7e0000000000,)
 DEFINES_msan = NDEBUG
 
@@ -328,6 +328,7 @@
 CXXFLAGS += -stdlib=libc++
 endif
 CPPFLAGS += -g -Wall -Wextra -Werror -Wno-long-long -Wno-unused-parameter -DOSATOMIC_USE_INLINED=1 -Ithird_party/abseil-cpp
+COREFLAGS += -fno-rtti -fno-exceptions
 LDFLAGS += -g
 
 CPPFLAGS += $(CPPFLAGS_$(CONFIG))
@@ -990,6 +991,7 @@
 gpr_histogram_test: $(BINDIR)/$(CONFIG)/gpr_histogram_test
 gpr_host_port_test: $(BINDIR)/$(CONFIG)/gpr_host_port_test
 gpr_log_test: $(BINDIR)/$(CONFIG)/gpr_log_test
+gpr_manual_constructor_test: $(BINDIR)/$(CONFIG)/gpr_manual_constructor_test
 gpr_mpscq_test: $(BINDIR)/$(CONFIG)/gpr_mpscq_test
 gpr_spinlock_test: $(BINDIR)/$(CONFIG)/gpr_spinlock_test
 gpr_stack_lockfree_test: $(BINDIR)/$(CONFIG)/gpr_stack_lockfree_test
@@ -1385,6 +1387,7 @@
   $(BINDIR)/$(CONFIG)/gpr_histogram_test \
   $(BINDIR)/$(CONFIG)/gpr_host_port_test \
   $(BINDIR)/$(CONFIG)/gpr_log_test \
+  $(BINDIR)/$(CONFIG)/gpr_manual_constructor_test \
   $(BINDIR)/$(CONFIG)/gpr_mpscq_test \
   $(BINDIR)/$(CONFIG)/gpr_spinlock_test \
   $(BINDIR)/$(CONFIG)/gpr_stack_lockfree_test \
@@ -1834,6 +1837,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/gpr_host_port_test || ( echo test gpr_host_port_test failed ; exit 1 )
 	$(E) "[RUN]     Testing gpr_log_test"
 	$(Q) $(BINDIR)/$(CONFIG)/gpr_log_test || ( echo test gpr_log_test failed ; exit 1 )
+	$(E) "[RUN]     Testing gpr_manual_constructor_test"
+	$(Q) $(BINDIR)/$(CONFIG)/gpr_manual_constructor_test || ( echo test gpr_manual_constructor_test failed ; exit 1 )
 	$(E) "[RUN]     Testing gpr_mpscq_test"
 	$(Q) $(BINDIR)/$(CONFIG)/gpr_mpscq_test || ( echo test gpr_mpscq_test failed ; exit 1 )
 	$(E) "[RUN]     Testing gpr_spinlock_test"
@@ -2584,6 +2589,16 @@
 	$(Q) mkdir -p `dirname $@`
 	$(Q) $(HOST_CXX) $(HOST_CXXFLAGS) $(HOST_CPPFLAGS) -MMD -MF $(addsuffix .dep, $(basename $@)) -c -o $@ $<
 
+$(OBJDIR)/$(CONFIG)/src/core/%.o : src/core/%.cc
+	$(E) "[CXX]     Compiling $<"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(COREFLAGS) -MMD -MF $(addsuffix .dep, $(basename $@)) -c -o $@ $<
+
+$(OBJDIR)/$(CONFIG)/test/core/%.o : test/core/%.cc
+	$(E) "[CXX]     Compiling $<"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(COREFLAGS) -MMD -MF $(addsuffix .dep, $(basename $@)) -c -o $@ $<
+
 $(OBJDIR)/$(CONFIG)/%.o : %.cc
 	$(E) "[CXX]     Compiling $<"
 	$(Q) mkdir -p `dirname $@`
@@ -10152,6 +10167,38 @@
 endif
 
 
+GPR_MANUAL_CONSTRUCTOR_TEST_SRC = \
+    test/core/support/manual_constructor_test.cc \
+
+GPR_MANUAL_CONSTRUCTOR_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GPR_MANUAL_CONSTRUCTOR_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/gpr_manual_constructor_test: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/gpr_manual_constructor_test: $(GPR_MANUAL_CONSTRUCTOR_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(GPR_MANUAL_CONSTRUCTOR_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/gpr_manual_constructor_test
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/support/manual_constructor_test.o:  $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_gpr_manual_constructor_test: $(GPR_MANUAL_CONSTRUCTOR_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GPR_MANUAL_CONSTRUCTOR_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 GPR_MPSCQ_TEST_SRC = \
     test/core/support/mpscq_test.cc \
 
diff --git a/WORKSPACE b/WORKSPACE
index 5f87d68..cead0bc 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -10,7 +10,7 @@
 
 bind(
     name = "zlib",
-    actual = "@submodule_zlib//:z",
+    actual = "@com_github_madler_zlib//:z",
 )
 
 bind(
@@ -35,22 +35,22 @@
 
 bind(
     name = "cares",
-    actual = "@submodule_cares//:ares",
+    actual = "@com_github_cares_cares//:ares",
 )
 
 bind(
     name = "gtest",
-    actual = "@submodule_gtest//:gtest",
+    actual = "@com_github_google_googletest//:gtest",
 )
 
 bind(
     name = "gmock",
-    actual = "@submodule_gtest//:gmock",
+    actual = "@com_github_google_googletest//:gmock",
 )
 
 bind(
     name = "benchmark",
-    actual = "@submodule_benchmark//:benchmark",
+    actual = "@com_github_google_benchmark//:benchmark",
 )
 
 bind(
@@ -58,47 +58,60 @@
     actual = "@com_github_gflags_gflags//:gflags",
 )
 
-local_repository(
+http_archive(
     name = "boringssl",
-    path = "third_party/boringssl-with-bazel",
+    # on the master-with-bazel branch
+    url = "https://boringssl.googlesource.com/boringssl/+archive/886e7d75368e3f4fab3f4d0d3584e4abfc557755.tar.gz",
 )
 
-new_local_repository(
-    name = "submodule_zlib",
+new_http_archive(
+    name = "com_github_madler_zlib",
     build_file = "third_party/zlib.BUILD",
-    path = "third_party/zlib",
+    strip_prefix = "zlib-cacf7f1d4e3d44d871b605da3b647f07d718623f",
+    url = "https://github.com/madler/zlib/archive/cacf7f1d4e3d44d871b605da3b647f07d718623f.tar.gz",
 )
 
-new_local_repository(
+http_archive(
     name = "com_google_protobuf",
-    build_file = "third_party/protobuf/BUILD",
-    path = "third_party/protobuf",
+    strip_prefix = "protobuf-2761122b810fe8861004ae785cc3ab39f384d342",
+    url = "https://github.com/google/protobuf/archive/2761122b810fe8861004ae785cc3ab39f384d342.tar.gz",
 )
 
-new_local_repository(
-    name = "submodule_gtest",
+new_http_archive(
+    name = "com_github_google_googletest",
     build_file = "third_party/gtest.BUILD",
-    path = "third_party/googletest",
+    strip_prefix = "googletest-ec44c6c1675c25b9827aacd08c02433cccde7780",
+    url = "https://github.com/google/googletest/archive/ec44c6c1675c25b9827aacd08c02433cccde7780.tar.gz",
 )
 
-local_repository(
+http_archive(
     name = "com_github_gflags_gflags",
-    path = "third_party/gflags",
+    strip_prefix = "gflags-30dbc81fb5ffdc98ea9b14b1918bfe4e8779b26e",
+    url = "https://github.com/gflags/gflags/archive/30dbc81fb5ffdc98ea9b14b1918bfe4e8779b26e.tar.gz",
 )
 
-new_local_repository(
-    name = "submodule_benchmark",
-    path = "third_party/benchmark",
+new_http_archive(
+    name = "com_github_google_benchmark",
     build_file = "third_party/benchmark.BUILD",
+    strip_prefix = "benchmark-5b7683f49e1e9223cf9927b24f6fd3d6bd82e3f8",
+    url = "https://github.com/google/benchmark/archive/5b7683f49e1e9223cf9927b24f6fd3d6bd82e3f8.tar.gz",
 )
 
 new_local_repository(
-    name = "submodule_cares",
+    name = "cares_local_files",
+    build_file = "third_party/cares/cares_local_files.BUILD",
     path = "third_party/cares",
-    build_file = "third_party/cares/cares.BUILD",
 )
 
-local_repository(
+new_http_archive(
+    name = "com_github_cares_cares",
+    build_file = "third_party/cares/cares.BUILD",
+    strip_prefix = "c-ares-3be1924221e1326df520f8498d704a5c4c8d0cce",
+    url = "https://github.com/c-ares/c-ares/archive/3be1924221e1326df520f8498d704a5c4c8d0cce.tar.gz",
+)
+
+http_archive(
     name = "com_google_absl",
-    path = "third_party/abseil-cpp",
+    strip_prefix = "abseil-cpp-cc4bed2d74f7c8717e31f9579214ab52a9c9c610",
+    url = "https://github.com/abseil/abseil-cpp/archive/cc4bed2d74f7c8717e31f9579214ab52a9c9c610.tar.gz",
 )
diff --git a/build.yaml b/build.yaml
index 4437aec..b440411 100644
--- a/build.yaml
+++ b/build.yaml
@@ -104,6 +104,7 @@
   - include/grpc/support/useful.h
   headers:
   - src/core/lib/profiling/timers.h
+  - src/core/lib/support/abstract.h
   - src/core/lib/support/arena.h
   - src/core/lib/support/atomic.h
   - src/core/lib/support/atomic_with_atm.h
@@ -2215,6 +2216,16 @@
   - gpr_test_util
   - gpr
   uses_polling: false
+- name: gpr_manual_constructor_test
+  cpu_cost: 3
+  build: test
+  language: c
+  src:
+  - test/core/support/manual_constructor_test.cc
+  deps:
+  - gpr_test_util
+  - gpr
+  uses_polling: false
 - name: gpr_mpscq_test
   cpu_cost: 30
   build: test
@@ -4916,8 +4927,8 @@
   msan:
     CC: clang
     CPPFLAGS: -O0 -fsanitize-coverage=edge -fsanitize=memory -fsanitize-memory-track-origins
-      -fno-omit-frame-pointer -DGTEST_HAS_TR1_TUPLE=0 -DGTEST_USE_OWN_TR1_TUPLE=1
-      -Wno-unused-command-line-argument -fPIE -pie -DGPR_NO_DIRECT_SYSCALLS
+      -fsanitize-memory-use-after-dtor -fno-omit-frame-pointer -DGTEST_HAS_TR1_TUPLE=0
+      -DGTEST_USE_OWN_TR1_TUPLE=1 -Wno-unused-command-line-argument -fPIE -pie -DGPR_NO_DIRECT_SYSCALLS
     CXX: clang++
     DEFINES: NDEBUG
     LD: clang++
@@ -4925,6 +4936,8 @@
       -fPIE -pie $(if $(JENKINS_BUILD),-Wl$(comma)-Ttext-segment=0x7e0000000000,)
     LDXX: clang++
     compile_the_world: true
+    test_environ:
+      MSAN_OPTIONS: poison_in_dtor=1
   mutrace:
     CPPFLAGS: -O3 -fno-omit-frame-pointer
     DEFINES: NDEBUG
@@ -4977,6 +4990,7 @@
     CPPFLAGS: -Ithird_party/boringssl/include -fvisibility=hidden -DOPENSSL_NO_ASM
       -D_GNU_SOURCE -DWIN32_LEAN_AND_MEAN -D_HAS_EXCEPTIONS=0 -DNOMINMAX
   global:
+    COREFLAGS: -fno-rtti -fno-exceptions
     CPPFLAGS: -g -Wall -Wextra -Werror -Wno-long-long -Wno-unused-parameter -DOSATOMIC_USE_INLINED=1
       -Ithird_party/abseil-cpp
     LDFLAGS: -g
diff --git a/examples/python/route_guide/route_guide_client.py b/examples/python/route_guide/route_guide_client.py
index 653f0d0..a0e32fb 100644
--- a/examples/python/route_guide/route_guide_client.py
+++ b/examples/python/route_guide/route_guide_client.py
@@ -17,7 +17,6 @@
 from __future__ import print_function
 
 import random
-import time
 
 import grpc
 
@@ -66,7 +65,6 @@
     random_feature = feature_list[random.randint(0, len(feature_list) - 1)]
     print("Visiting point %s" % random_feature.location)
     yield random_feature.location
-    time.sleep(random.uniform(0.5, 1.5))
 
 
 def guide_record_route(stub):
@@ -91,7 +89,6 @@
   for msg in messages:
     print("Sending %s at %s" % (msg.message, msg.location))
     yield msg
-    time.sleep(random.uniform(0.5, 1.0))
 
 
 def guide_route_chat(stub):
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 02c6a64..2f97565 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -187,6 +187,7 @@
 
     # To save you from scrolling, this is the last part of the podspec.
     ss.source_files = 'src/core/lib/profiling/timers.h',
+                      'src/core/lib/support/abstract.h',
                       'src/core/lib/support/arena.h',
                       'src/core/lib/support/atomic.h',
                       'src/core/lib/support/atomic_with_atm.h',
@@ -706,6 +707,7 @@
                       'src/core/plugin_registry/grpc_plugin_registry.cc'
 
     ss.private_header_files = 'src/core/lib/profiling/timers.h',
+                              'src/core/lib/support/abstract.h',
                               'src/core/lib/support/arena.h',
                               'src/core/lib/support/atomic.h',
                               'src/core/lib/support/atomic_with_atm.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index d674c09..0dd7ceb 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -84,6 +84,7 @@
   s.files += %w( include/grpc/impl/codegen/sync_posix.h )
   s.files += %w( include/grpc/impl/codegen/sync_windows.h )
   s.files += %w( src/core/lib/profiling/timers.h )
+  s.files += %w( src/core/lib/support/abstract.h )
   s.files += %w( src/core/lib/support/arena.h )
   s.files += %w( src/core/lib/support/atomic.h )
   s.files += %w( src/core/lib/support/atomic_with_atm.h )
diff --git a/include/grpc++/impl/codegen/call.h b/include/grpc++/impl/codegen/call.h
index 1a98829..41e9586 100644
--- a/include/grpc++/impl/codegen/call.h
+++ b/include/grpc++/impl/codegen/call.h
@@ -579,6 +579,7 @@
     op->data.recv_status_on_client.trailing_metadata = metadata_map_->arr();
     op->data.recv_status_on_client.status = &status_code_;
     op->data.recv_status_on_client.status_details = &error_message_;
+    op->data.recv_status_on_client.error_string = nullptr;
     op->flags = 0;
     op->reserved = NULL;
   }
diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h
index 5cd04b3..73669d9 100644
--- a/include/grpc/impl/codegen/grpc_types.h
+++ b/include/grpc/impl/codegen/grpc_types.h
@@ -561,6 +561,10 @@
       grpc_metadata_array* trailing_metadata;
       grpc_status_code* status;
       grpc_slice* status_details;
+      /** If this is not nullptr, it will be populated with the full fidelity
+       * error string for debugging purposes. The application is responsible
+       * for freeing the data. */
+      const char** error_string;
     } recv_status_on_client;
     struct grpc_op_recv_close_on_server {
       /** out argument, set to 1 if the call failed in any way (seen as a
diff --git a/package.xml b/package.xml
index 3356c27..59d49dd 100644
--- a/package.xml
+++ b/package.xml
@@ -96,6 +96,7 @@
     <file baseinstalldir="/" name="include/grpc/impl/codegen/sync_posix.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/sync_windows.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/profiling/timers.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/support/abstract.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/support/arena.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/support/atomic.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/support/atomic_with_atm.h" role="src" />
diff --git a/src/compiler/cpp_generator.cc b/src/compiler/cpp_generator.cc
index 3adf0fa..dec8cd0 100644
--- a/src/compiler/cpp_generator.cc
+++ b/src/compiler/cpp_generator.cc
@@ -104,7 +104,7 @@
     grpc::string leading_comments = file->GetLeadingComments("//");
     if (!leading_comments.empty()) {
       printer->Print(vars, "// Original file comments:\n");
-      printer->Print(leading_comments.c_str());
+      printer->PrintRaw(leading_comments.c_str());
     }
     printer->Print(vars, "#ifndef GRPC_$filename_identifier$__INCLUDED\n");
     printer->Print(vars, "#define GRPC_$filename_identifier$__INCLUDED\n");
diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc
index e0957bf..40fe0b0 100644
--- a/src/compiler/csharp_generator.cc
+++ b/src/compiler/csharp_generator.cc
@@ -666,7 +666,7 @@
     grpc::string leading_comments = GetCsharpComments(file, true);
     if (!leading_comments.empty()) {
       out.Print("// Original file comments:\n");
-      out.Print(leading_comments.c_str());
+      out.PrintRaw(leading_comments.c_str());
     }
 
     out.Print("#pragma warning disable 1591\n");
diff --git a/src/compiler/node_generator.cc b/src/compiler/node_generator.cc
index ed835b4..661587c 100644
--- a/src/compiler/node_generator.cc
+++ b/src/compiler/node_generator.cc
@@ -250,7 +250,7 @@
     grpc::string leading_comments = GetNodeComments(file, true);
     if (!leading_comments.empty()) {
       out.Print("// Original file comments:\n");
-      out.Print(leading_comments.c_str());
+      out.PrintRaw(leading_comments.c_str());
     }
 
     out.Print("'use strict';\n");
diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc
index 349f1dc..8fe30e9 100644
--- a/src/compiler/objective_c_generator.cc
+++ b/src/compiler/objective_c_generator.cc
@@ -65,7 +65,7 @@
     printer->Print(" * ");
     size_t start_pos = it->find_first_not_of(' ');
     if (start_pos != grpc::string::npos) {
-      printer->Print(it->c_str() + start_pos);
+      printer->PrintRaw(it->c_str() + start_pos);
     }
     printer->Print("\n");
   }
diff --git a/src/compiler/php_generator.cc b/src/compiler/php_generator.cc
index 1ff9520..d9705e8 100644
--- a/src/compiler/php_generator.cc
+++ b/src/compiler/php_generator.cc
@@ -164,7 +164,7 @@
     grpc::string leading_comments = GetPHPComments(file, "//");
     if (!leading_comments.empty()) {
       out.Print("// Original file comments:\n");
-      out.Print(leading_comments.c_str());
+      out.PrintRaw(leading_comments.c_str());
     }
 
     map<grpc::string, grpc::string> vars;
diff --git a/src/compiler/protobuf_plugin.h b/src/compiler/protobuf_plugin.h
index 1551908..b971af1 100644
--- a/src/compiler/protobuf_plugin.h
+++ b/src/compiler/protobuf_plugin.h
@@ -141,6 +141,7 @@
   }
 
   void Print(const char* string) { printer_.Print(string); }
+  void PrintRaw(const char* string) { printer_.PrintRaw(string); }
   void Indent() { printer_.Indent(); }
   void Outdent() { printer_.Outdent(); }
 
diff --git a/src/compiler/python_generator.cc b/src/compiler/python_generator.cc
index c1ae7d3..8a0b889 100644
--- a/src/compiler/python_generator.cc
+++ b/src/compiler/python_generator.cc
@@ -101,7 +101,7 @@
        ++it) {
     size_t start_pos = it->find_first_not_of(' ');
     if (start_pos != grpc::string::npos) {
-      out->Print(it->c_str() + start_pos);
+      out->PrintRaw(it->c_str() + start_pos);
     }
     out->Print("\n");
   }
diff --git a/src/compiler/ruby_generator.cc b/src/compiler/ruby_generator.cc
index ed7e074..e81dea6 100644
--- a/src/compiler/ruby_generator.cc
+++ b/src/compiler/ruby_generator.cc
@@ -174,7 +174,7 @@
     grpc::string leading_comments = GetRubyComments(file, true);
     if (!leading_comments.empty()) {
       out.Print("# Original file comments:\n");
-      out.Print(leading_comments.c_str());
+      out.PrintRaw(leading_comments.c_str());
     }
 
     out.Print("\n");
diff --git a/src/compiler/schema_interface.h b/src/compiler/schema_interface.h
index b2021f2..c000478 100644
--- a/src/compiler/schema_interface.h
+++ b/src/compiler/schema_interface.h
@@ -86,6 +86,7 @@
   virtual void Print(const std::map<grpc::string, grpc::string>& vars,
                      const char* template_string) = 0;
   virtual void Print(const char* string) = 0;
+  virtual void PrintRaw(const char* string) = 0;
   virtual void Indent() = 0;
   virtual void Outdent() = 0;
 };
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
index 635034d..607fbf3 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
@@ -1758,7 +1758,7 @@
   grpc_http2_error_code http_error;
   grpc_slice slice;
   grpc_error_get_status(exec_ctx, error, GRPC_MILLIS_INF_FUTURE, nullptr,
-                        &slice, &http_error);
+                        &slice, &http_error, nullptr);
   grpc_chttp2_goaway_append(t->last_new_stream_id, (uint32_t)http_error,
                             grpc_slice_ref_internal(slice), &t->qbuf);
   grpc_chttp2_initiate_write(exec_ctx, t,
@@ -2065,7 +2065,7 @@
     if (s->id != 0) {
       grpc_http2_error_code http_error;
       grpc_error_get_status(exec_ctx, due_to_error, s->deadline, nullptr,
-                            nullptr, &http_error);
+                            nullptr, &http_error, nullptr);
       grpc_slice_buffer_add(
           &t->qbuf, grpc_chttp2_rst_stream_create(s->id, (uint32_t)http_error,
                                                   &s->stats.outgoing));
@@ -2083,7 +2083,8 @@
                              grpc_chttp2_stream* s, grpc_error* error) {
   grpc_status_code status;
   grpc_slice slice;
-  grpc_error_get_status(exec_ctx, error, s->deadline, &status, &slice, nullptr);
+  grpc_error_get_status(exec_ctx, error, s->deadline, &status, &slice, nullptr,
+                        nullptr);
   if (status != GRPC_STATUS_OK) {
     s->seen_error = true;
   }
@@ -2248,7 +2249,7 @@
   grpc_status_code grpc_status;
   grpc_slice slice;
   grpc_error_get_status(exec_ctx, error, s->deadline, &grpc_status, &slice,
-                        nullptr);
+                        nullptr, nullptr);
 
   GPR_ASSERT(grpc_status >= 0 && (int)grpc_status < 100);
 
diff --git a/src/core/ext/transport/chttp2/transport/flow_control.h b/src/core/ext/transport/chttp2/transport/flow_control.h
index bb710fe..2515c94 100644
--- a/src/core/ext/transport/chttp2/transport/flow_control.h
+++ b/src/core/ext/transport/chttp2/transport/flow_control.h
@@ -19,6 +19,7 @@
 #ifndef GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FLOW_CONTROL_H
 #define GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FLOW_CONTROL_H
 
+#include <grpc/support/port_platform.h>
 #include <stdint.h>
 
 #include <grpc/support/useful.h>
diff --git a/src/core/lib/channel/channel_stack.h b/src/core/lib/channel/channel_stack.h
index 6b41ad1..830c112 100644
--- a/src/core/lib/channel/channel_stack.h
+++ b/src/core/lib/channel/channel_stack.h
@@ -84,6 +84,7 @@
 typedef struct {
   grpc_call_stats stats;
   grpc_status_code final_status;
+  const char** error_string;
 } grpc_call_final_info;
 
 /* Channel filters specify:
diff --git a/src/core/lib/iomgr/ev_epoll1_linux.cc b/src/core/lib/iomgr/ev_epoll1_linux.cc
index 60446f7..0dda1d9 100644
--- a/src/core/lib/iomgr/ev_epoll1_linux.cc
+++ b/src/core/lib/iomgr/ev_epoll1_linux.cc
@@ -263,11 +263,13 @@
 
   if (new_fd == nullptr) {
     new_fd = (grpc_fd*)gpr_malloc(sizeof(grpc_fd));
+    new_fd->read_closure.Init();
+    new_fd->write_closure.Init();
   }
 
   new_fd->fd = fd;
-  new_fd->read_closure.Init();
-  new_fd->write_closure.Init();
+  new_fd->read_closure->InitEvent();
+  new_fd->write_closure->InitEvent();
   gpr_atm_no_barrier_store(&new_fd->read_notifier_pollset, (gpr_atm)NULL);
 
   new_fd->freelist_next = nullptr;
@@ -336,8 +338,8 @@
   GRPC_CLOSURE_SCHED(exec_ctx, on_done, GRPC_ERROR_REF(error));
 
   grpc_iomgr_unregister_object(&fd->iomgr_object);
-  fd->read_closure.Destroy();
-  fd->write_closure.Destroy();
+  fd->read_closure->DestroyEvent();
+  fd->write_closure->DestroyEvent();
 
   gpr_mu_lock(&fd_freelist_mu);
   fd->freelist_next = fd_freelist;
diff --git a/src/core/lib/iomgr/ev_epollex_linux.cc b/src/core/lib/iomgr/ev_epollex_linux.cc
index 10b8440..62643df 100644
--- a/src/core/lib/iomgr/ev_epollex_linux.cc
+++ b/src/core/lib/iomgr/ev_epollex_linux.cc
@@ -286,8 +286,8 @@
   fd->freelist_next = fd_freelist;
   fd_freelist = fd;
 
-  fd->read_closure.Destroy();
-  fd->write_closure.Destroy();
+  fd->read_closure->DestroyEvent();
+  fd->write_closure->DestroyEvent();
 
   gpr_mu_unlock(&fd_freelist_mu);
 }
@@ -340,6 +340,8 @@
 
   if (new_fd == nullptr) {
     new_fd = (grpc_fd*)gpr_malloc(sizeof(grpc_fd));
+    new_fd->read_closure.Init();
+    new_fd->write_closure.Init();
   }
 
   gpr_mu_init(&new_fd->pollable_mu);
@@ -347,8 +349,8 @@
   new_fd->pollable_obj = nullptr;
   gpr_atm_rel_store(&new_fd->refst, (gpr_atm)1);
   new_fd->fd = fd;
-  new_fd->read_closure.Init();
-  new_fd->write_closure.Init();
+  new_fd->read_closure->InitEvent();
+  new_fd->write_closure->InitEvent();
   gpr_atm_no_barrier_store(&new_fd->read_notifier_pollset, (gpr_atm)NULL);
 
   new_fd->freelist_next = nullptr;
diff --git a/src/core/lib/iomgr/ev_epollsig_linux.cc b/src/core/lib/iomgr/ev_epollsig_linux.cc
index 689c0d4..12c8483 100644
--- a/src/core/lib/iomgr/ev_epollsig_linux.cc
+++ b/src/core/lib/iomgr/ev_epollsig_linux.cc
@@ -767,8 +767,8 @@
     fd_freelist = fd;
     grpc_iomgr_unregister_object(&fd->iomgr_object);
 
-    fd->read_closure.Destroy();
-    fd->write_closure.Destroy();
+    fd->read_closure->DestroyEvent();
+    fd->write_closure->DestroyEvent();
 
     gpr_mu_unlock(&fd_freelist_mu);
   } else {
@@ -819,6 +819,8 @@
   if (new_fd == nullptr) {
     new_fd = (grpc_fd*)gpr_malloc(sizeof(grpc_fd));
     gpr_mu_init(&new_fd->po.mu);
+    new_fd->read_closure.Init();
+    new_fd->write_closure.Init();
   }
 
   /* Note: It is not really needed to get the new_fd->po.mu lock here. If this
@@ -833,8 +835,8 @@
   gpr_atm_rel_store(&new_fd->refst, (gpr_atm)1);
   new_fd->fd = fd;
   new_fd->orphaned = false;
-  new_fd->read_closure.Init();
-  new_fd->write_closure.Init();
+  new_fd->read_closure->InitEvent();
+  new_fd->write_closure->InitEvent();
   gpr_atm_no_barrier_store(&new_fd->read_notifier_pollset, (gpr_atm)NULL);
 
   new_fd->freelist_next = nullptr;
diff --git a/src/core/lib/iomgr/lockfree_event.cc b/src/core/lib/iomgr/lockfree_event.cc
index d477f64..f0e798e 100644
--- a/src/core/lib/iomgr/lockfree_event.cc
+++ b/src/core/lib/iomgr/lockfree_event.cc
@@ -57,7 +57,9 @@
 
 namespace grpc_core {
 
-LockfreeEvent::LockfreeEvent() {
+LockfreeEvent::LockfreeEvent() { InitEvent(); }
+
+void LockfreeEvent::InitEvent() {
   /* Perform an atomic store to start the state machine.
 
      Note carefully that LockfreeEvent *MAY* be used whilst in a destroyed
@@ -67,7 +69,7 @@
   gpr_atm_no_barrier_store(&state_, kClosureNotReady);
 }
 
-LockfreeEvent::~LockfreeEvent() {
+void LockfreeEvent::DestroyEvent() {
   gpr_atm curr;
   do {
     curr = gpr_atm_no_barrier_load(&state_);
diff --git a/src/core/lib/iomgr/lockfree_event.h b/src/core/lib/iomgr/lockfree_event.h
index c667dcd..aec67a3 100644
--- a/src/core/lib/iomgr/lockfree_event.h
+++ b/src/core/lib/iomgr/lockfree_event.h
@@ -30,11 +30,16 @@
 class LockfreeEvent {
  public:
   LockfreeEvent();
-  ~LockfreeEvent();
 
   LockfreeEvent(const LockfreeEvent&) = delete;
   LockfreeEvent& operator=(const LockfreeEvent&) = delete;
 
+  // These methods are used to initialize and destroy the internal state. These
+  // cannot be done in constructor and destructor because SetReady may be called
+  // when the event is destroyed and put in a freelist.
+  void InitEvent();
+  void DestroyEvent();
+
   bool IsShutdown() const {
     return (gpr_atm_no_barrier_load(&state_) & kShutdownBit) != 0;
   }
diff --git a/src/core/lib/iomgr/sockaddr_utils.cc b/src/core/lib/iomgr/sockaddr_utils.cc
index 3477fb5..0c0a2fe 100644
--- a/src/core/lib/iomgr/sockaddr_utils.cc
+++ b/src/core/lib/iomgr/sockaddr_utils.cc
@@ -148,7 +148,7 @@
   grpc_resolved_address addr_normalized;
   char ntop_buf[INET6_ADDRSTRLEN];
   const void* ip = nullptr;
-  int port;
+  int port = 0;
   uint32_t sin6_scope_id = 0;
   int ret;
 
diff --git a/src/core/lib/support/abstract.h b/src/core/lib/support/abstract.h
new file mode 100644
index 0000000..5498769
--- /dev/null
+++ b/src/core/lib/support/abstract.h
@@ -0,0 +1,29 @@
+/*
+ *
+ * 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_SUPPORT_ABSTRACT_H
+#define GRPC_CORE_LIB_SUPPORT_ABSTRACT_H
+
+// This is needed to support abstract base classes in the c core. Since gRPC
+// doesn't have a c++ runtime, it will hit a linker error on delete unless
+// we define a virtual operator delete. See this blog for more info:
+// https://eli.thegreenplace.net/2015/c-deleting-destructors-and-virtual-operator-delete/
+#define GRPC_ABSTRACT_BASE_CLASS \
+  static void operator delete(void* p) { abort(); }
+
+#endif /* GRPC_CORE_LIB_SUPPORT_ABSTRACT_H */
diff --git a/src/core/lib/support/manual_constructor.h b/src/core/lib/support/manual_constructor.h
index d753cf9..fda7653 100644
--- a/src/core/lib/support/manual_constructor.h
+++ b/src/core/lib/support/manual_constructor.h
@@ -22,12 +22,147 @@
 // manually construct a region of memory with some type
 
 #include <stddef.h>
+#include <stdlib.h>
 #include <new>
 #include <type_traits>
 #include <utility>
 
+#include <grpc/support/log.h>
+
 namespace grpc_core {
 
+// this contains templated helpers needed to implement the ManualConstructors
+// in this file.
+namespace manual_ctor_impl {
+
+// is_one_of returns true it a class, Member, is present in a variadic list of
+// classes, List.
+template <class Member, class... List>
+class is_one_of;
+
+template <class Member, class... List>
+class is_one_of<Member, Member, List...> {
+ public:
+  static constexpr const bool value = true;
+};
+
+template <class Member, class A, class... List>
+class is_one_of<Member, A, List...> {
+ public:
+  static constexpr const bool value = is_one_of<Member, List...>::value;
+};
+
+template <class Member>
+class is_one_of<Member> {
+ public:
+  static constexpr const bool value = false;
+};
+
+// max_size_of returns sizeof(Type) for the largest type in the variadic list
+// of classes, Types.
+template <class... Types>
+class max_size_of;
+
+template <class A>
+class max_size_of<A> {
+ public:
+  static constexpr const size_t value = sizeof(A);
+};
+
+template <class A, class... B>
+class max_size_of<A, B...> {
+ public:
+  static constexpr const size_t value = sizeof(A) > max_size_of<B...>::value
+                                            ? sizeof(A)
+                                            : max_size_of<B...>::value;
+};
+
+// max_size_of returns alignof(Type) for the largest type in the variadic list
+// of classes, Types.
+template <class... Types>
+class max_align_of;
+
+template <class A>
+class max_align_of<A> {
+ public:
+  static constexpr const size_t value = alignof(A);
+};
+
+template <class A, class... B>
+class max_align_of<A, B...> {
+ public:
+  static constexpr const size_t value = alignof(A) > max_align_of<B...>::value
+                                            ? alignof(A)
+                                            : max_align_of<B...>::value;
+};
+
+}  // namespace manual_ctor_impl
+
+template <class BaseType, class... DerivedTypes>
+class PolymorphicManualConstructor {
+ public:
+  // No constructor or destructor because one of the most useful uses of
+  // this class is as part of a union, and members of a union could not have
+  // constructors or destructors till C++11.  And, anyway, the whole point of
+  // this class is to bypass constructor and destructor.
+
+  BaseType* get() { return reinterpret_cast<BaseType*>(&space_); }
+  const BaseType* get() const {
+    return reinterpret_cast<const BaseType*>(&space_);
+  }
+
+  BaseType* operator->() { return get(); }
+  const BaseType* operator->() const { return get(); }
+
+  BaseType& operator*() { return *get(); }
+  const BaseType& operator*() const { return *get(); }
+
+  template <class DerivedType>
+  void Init() {
+    FinishInit(new (&space_) DerivedType);
+  }
+
+  // Init() constructs the Type instance using the given arguments
+  // (which are forwarded to Type's constructor).
+  //
+  // Note that Init() with no arguments performs default-initialization,
+  // not zero-initialization (i.e it behaves the same as "new Type;", not
+  // "new Type();"), so it will leave non-class types uninitialized.
+  template <class DerivedType, typename... Ts>
+  void Init(Ts&&... args) {
+    FinishInit(new (&space_) DerivedType(std::forward<Ts>(args)...));
+  }
+
+  // Init() that is equivalent to copy and move construction.
+  // Enables usage like this:
+  //   ManualConstructor<std::vector<int>> v;
+  //   v.Init({1, 2, 3});
+  template <class DerivedType>
+  void Init(const DerivedType& x) {
+    FinishInit(new (&space_) DerivedType(x));
+  }
+  template <class DerivedType>
+  void Init(DerivedType&& x) {
+    FinishInit(new (&space_) DerivedType(std::move(x)));
+  }
+
+  void Destroy() { get()->~BaseType(); }
+
+ private:
+  template <class DerivedType>
+  void FinishInit(DerivedType* p) {
+    static_assert(
+        manual_ctor_impl::is_one_of<DerivedType, DerivedTypes...>::value,
+        "DerivedType must be one of the predeclared DerivedTypes");
+    GPR_ASSERT(reinterpret_cast<BaseType*>(static_cast<DerivedType*>(p)) == p);
+  }
+
+  typename std::aligned_storage<
+      grpc_core::manual_ctor_impl::max_size_of<DerivedTypes...>::value,
+      grpc_core::manual_ctor_impl::max_align_of<DerivedTypes...>::value>::type
+      space_;
+};
+
 template <typename Type>
 class ManualConstructor {
  public:
diff --git a/src/core/lib/surface/call.cc b/src/core/lib/surface/call.cc
index a83c95c..a2eb02b 100644
--- a/src/core/lib/surface/call.cc
+++ b/src/core/lib/surface/call.cc
@@ -234,6 +234,7 @@
     struct {
       grpc_status_code* status;
       grpc_slice* status_details;
+      const char** error_string;
     } client;
     struct {
       int* cancelled;
@@ -284,7 +285,8 @@
 static void get_final_status(grpc_exec_ctx* exec_ctx, grpc_call* call,
                              void (*set_value)(grpc_status_code code,
                                                void* user_data),
-                             void* set_value_user_data, grpc_slice* details);
+                             void* set_value_user_data, grpc_slice* details,
+                             const char** error_string);
 static void set_status_value_directly(grpc_status_code status, void* dest);
 static void set_status_from_error(grpc_exec_ctx* exec_ctx, grpc_call* call,
                                   status_source source, grpc_error* error);
@@ -546,7 +548,8 @@
   }
 
   get_final_status(exec_ctx, c, set_status_value_directly,
-                   &c->final_info.final_status, nullptr);
+                   &c->final_info.final_status, nullptr,
+                   c->final_info.error_string);
   c->final_info.stats.latency =
       gpr_time_sub(gpr_now(GPR_CLOCK_MONOTONIC), c->start_time);
 
@@ -733,16 +736,15 @@
  * FINAL STATUS CODE MANIPULATION
  */
 
-static bool get_final_status_from(grpc_exec_ctx* exec_ctx, grpc_call* call,
-                                  grpc_error* error, bool allow_ok_status,
-                                  void (*set_value)(grpc_status_code code,
-                                                    void* user_data),
-                                  void* set_value_user_data,
-                                  grpc_slice* details) {
+static bool get_final_status_from(
+    grpc_exec_ctx* exec_ctx, grpc_call* call, grpc_error* error,
+    bool allow_ok_status,
+    void (*set_value)(grpc_status_code code, void* user_data),
+    void* set_value_user_data, grpc_slice* details, const char** error_string) {
   grpc_status_code code;
   grpc_slice slice = grpc_empty_slice();
   grpc_error_get_status(exec_ctx, error, call->send_deadline, &code, &slice,
-                        nullptr);
+                        nullptr, error_string);
   if (code == GRPC_STATUS_OK && !allow_ok_status) {
     return false;
   }
@@ -757,7 +759,8 @@
 static void get_final_status(grpc_exec_ctx* exec_ctx, grpc_call* call,
                              void (*set_value)(grpc_status_code code,
                                                void* user_data),
-                             void* set_value_user_data, grpc_slice* details) {
+                             void* set_value_user_data, grpc_slice* details,
+                             const char** error_string) {
   int i;
   received_status status[STATUS_SOURCE_COUNT];
   for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
@@ -781,7 +784,7 @@
           grpc_error_has_clear_grpc_status(status[i].error)) {
         if (get_final_status_from(exec_ctx, call, status[i].error,
                                   allow_ok_status != 0, set_value,
-                                  set_value_user_data, details)) {
+                                  set_value_user_data, details, error_string)) {
           return;
         }
       }
@@ -791,7 +794,7 @@
       if (status[i].is_set) {
         if (get_final_status_from(exec_ctx, call, status[i].error,
                                   allow_ok_status != 0, set_value,
-                                  set_value_user_data, details)) {
+                                  set_value_user_data, details, error_string)) {
           return;
         }
       }
@@ -1332,10 +1335,11 @@
     if (call->is_client) {
       get_final_status(exec_ctx, call, set_status_value_directly,
                        call->final_op.client.status,
-                       call->final_op.client.status_details);
+                       call->final_op.client.status_details,
+                       call->final_op.client.error_string);
     } else {
       get_final_status(exec_ctx, call, set_cancelled_value,
-                       call->final_op.server.cancelled, nullptr);
+                       call->final_op.server.cancelled, nullptr, nullptr);
     }
 
     GRPC_ERROR_UNREF(error);
@@ -1992,6 +1996,8 @@
         call->final_op.client.status = op->data.recv_status_on_client.status;
         call->final_op.client.status_details =
             op->data.recv_status_on_client.status_details;
+        call->final_op.client.error_string =
+            op->data.recv_status_on_client.error_string;
         stream_op->recv_trailing_metadata = true;
         stream_op->collect_stats = true;
         stream_op_payload->recv_trailing_metadata.recv_trailing_metadata =
diff --git a/src/core/lib/transport/error_utils.cc b/src/core/lib/transport/error_utils.cc
index 19510b4..69c8ae6 100644
--- a/src/core/lib/transport/error_utils.cc
+++ b/src/core/lib/transport/error_utils.cc
@@ -18,6 +18,7 @@
 
 #include "src/core/lib/transport/error_utils.h"
 
+#include <grpc/support/string_util.h>
 #include "src/core/lib/iomgr/error_internal.h"
 #include "src/core/lib/transport/status_conversion.h"
 
@@ -41,8 +42,8 @@
 
 void grpc_error_get_status(grpc_exec_ctx* exec_ctx, grpc_error* error,
                            grpc_millis deadline, grpc_status_code* code,
-                           grpc_slice* slice,
-                           grpc_http2_error_code* http_error) {
+                           grpc_slice* slice, grpc_http2_error_code* http_error,
+                           const char** error_string) {
   // 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 =
@@ -69,6 +70,10 @@
   }
   if (code != nullptr) *code = status;
 
+  if (error_string != NULL && status != GRPC_STATUS_OK) {
+    *error_string = gpr_strdup(grpc_error_string(error));
+  }
+
   if (http_error != nullptr) {
     if (grpc_error_get_int(found_error, GRPC_ERROR_INT_HTTP2_ERROR, &integer)) {
       *http_error = (grpc_http2_error_code)integer;
diff --git a/src/core/lib/transport/error_utils.h b/src/core/lib/transport/error_utils.h
index 690e420..6f21f48 100644
--- a/src/core/lib/transport/error_utils.h
+++ b/src/core/lib/transport/error_utils.h
@@ -30,13 +30,15 @@
 /// A utility function to get the status code and message to be returned
 /// to the application.  If not set in the top-level message, looks
 /// through child errors until it finds the first one with these attributes.
-/// All attributes are pulled from the same child error. If any of the
-/// attributes (code, msg, http_status) are unneeded, they can be passed as
+/// All attributes are pulled from the same child error. error_string will
+/// be populated with the entire error string. If any of the attributes (code,
+/// msg, http_status, error_string) are unneeded, they can be passed as
 /// NULL.
 void grpc_error_get_status(grpc_exec_ctx* exec_ctx, grpc_error* error,
                            grpc_millis deadline, grpc_status_code* code,
                            grpc_slice* slice,
-                           grpc_http2_error_code* http_status);
+                           grpc_http2_error_code* http_status,
+                           const char** error_string);
 
 /// A utility function to check whether there is a clear status code that
 /// doesn't need to be guessed in \a error. This means that \a error or some
diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
index 9488ce2..e7d8939 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs
@@ -64,7 +64,7 @@
         public void CancelNotificationAfterStartDisposes()
         {
             var finishedTask = asyncCallServer.ServerSideCallAsync();
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
             AssertFinished(asyncCallServer, fakeCall, finishedTask);
         }
 
@@ -76,8 +76,8 @@
 
             var moveNextTask = requestStream.MoveNext();
 
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
-            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
             Assert.IsFalse(moveNextTask.Result);
 
             AssertFinished(asyncCallServer, fakeCall, finishedTask);
@@ -89,7 +89,7 @@
             var finishedTask = asyncCallServer.ServerSideCallAsync();
             var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
 
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
 
             // Check that starting a read after cancel notification has been processed is legal.
             var moveNextTask = requestStream.MoveNext();
@@ -107,10 +107,10 @@
             // 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.ReceivedMessageHandler(false, null);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, null);
             Assert.IsFalse(moveNextTask.Result);
 
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
             AssertFinished(asyncCallServer, fakeCall, finishedTask);
         }
 
@@ -120,7 +120,7 @@
             var finishedTask = asyncCallServer.ServerSideCallAsync();
             var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
 
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
 
             // TODO(jtattermusch): should we throw a different exception type instead?
             Assert.Throws(typeof(InvalidOperationException), () => responseStream.WriteAsync("request1"));
@@ -134,10 +134,10 @@
             var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
 
             var writeTask = responseStream.WriteAsync("request1");
-            fakeCall.SendCompletionHandler(false);
+            fakeCall.SendCompletionCallback.OnSendCompletion(false);
             Assert.ThrowsAsync(typeof(IOException), async () => await writeTask);
 
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
             AssertFinished(asyncCallServer, fakeCall, finishedTask);
         }
 
@@ -150,13 +150,13 @@
             var writeTask = responseStream.WriteAsync("request1");
             var writeStatusTask = asyncCallServer.SendStatusFromServerAsync(Status.DefaultSuccess, new Metadata(), null);
 
-            fakeCall.SendCompletionHandler(true);
-            fakeCall.SendStatusFromServerHandler(true);
+            fakeCall.SendCompletionCallback.OnSendCompletion(true);
+            fakeCall.SendStatusFromServerCallback.OnSendStatusFromServerCompletion(true);
 
             Assert.DoesNotThrowAsync(async () => await writeTask);
             Assert.DoesNotThrowAsync(async () => await writeStatusTask);
 
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
 
             AssertFinished(asyncCallServer, fakeCall, finishedTask);
         }
@@ -170,8 +170,8 @@
             asyncCallServer.SendStatusFromServerAsync(Status.DefaultSuccess, new Metadata(), null);
             Assert.ThrowsAsync(typeof(InvalidOperationException), async () => await responseStream.WriteAsync("request1"));
 
-            fakeCall.SendStatusFromServerHandler(true);
-            fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
+            fakeCall.SendStatusFromServerCallback.OnSendStatusFromServerCompletion(true);
+            fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true);
 
             AssertFinished(asyncCallServer, fakeCall, finishedTask);
         }
diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
index b2b49f3..9aab54d 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
@@ -73,7 +73,7 @@
         public void AsyncUnary_Success()
         {
             var resultTask = asyncCall.UnaryCallAsync("request1");
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
                 CreateResponsePayload(),
                 new Metadata());
@@ -85,7 +85,7 @@
         public void AsyncUnary_NonSuccessStatusCode()
         {
             var resultTask = asyncCall.UnaryCallAsync("request1");
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.InvalidArgument),
                 null,
                 new Metadata());
@@ -97,7 +97,7 @@
         public void AsyncUnary_NullResponsePayload()
         {
             var resultTask = asyncCall.UnaryCallAsync("request1");
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
                 null,
                 new Metadata());
@@ -118,7 +118,7 @@
         public void ClientStreaming_NoRequest_Success()
         {
             var resultTask = asyncCall.ClientStreamingCallAsync();
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
                 CreateResponsePayload(),
                 new Metadata());
@@ -130,7 +130,7 @@
         public void ClientStreaming_NoRequest_NonSuccessStatusCode()
         {
             var resultTask = asyncCall.ClientStreamingCallAsync();
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.InvalidArgument),
                 null,
                 new Metadata());
@@ -145,18 +145,18 @@
             var requestStream = new ClientRequestStream<string, string>(asyncCall);
 
             var writeTask = requestStream.WriteAsync("request1");
-            fakeCall.SendCompletionHandler(true);
+            fakeCall.SendCompletionCallback.OnSendCompletion(true);
             writeTask.Wait();
 
             var writeTask2 = requestStream.WriteAsync("request2");
-            fakeCall.SendCompletionHandler(true);
+            fakeCall.SendCompletionCallback.OnSendCompletion(true);
             writeTask2.Wait();
 
             var completeTask = requestStream.CompleteAsync();
-            fakeCall.SendCompletionHandler(true);
+            fakeCall.SendCompletionCallback.OnSendCompletion(true);
             completeTask.Wait();
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
                 CreateResponsePayload(),
                 new Metadata());
@@ -171,12 +171,12 @@
             var requestStream = new ClientRequestStream<string, string>(asyncCall);
 
             var writeTask = requestStream.WriteAsync("request1");
-            fakeCall.SendCompletionHandler(false);
+            fakeCall.SendCompletionCallback.OnSendCompletion(false);
 
             // The write will wait for call to finish to receive the status code.
             Assert.IsFalse(writeTask.IsCompleted);
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Internal),
                 null,
                 new Metadata());
@@ -195,12 +195,12 @@
 
             var writeTask = requestStream.WriteAsync("request1");
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Internal),
                 null,
                 new Metadata());
 
-            fakeCall.SendCompletionHandler(false);
+            fakeCall.SendCompletionCallback.OnSendCompletion(false);
 
             var ex = Assert.ThrowsAsync<RpcException>(async () => await writeTask);
             Assert.AreEqual(StatusCode.Internal, ex.Status.StatusCode);
@@ -215,13 +215,13 @@
             var requestStream = new ClientRequestStream<string, string>(asyncCall);
 
             var writeTask = requestStream.WriteAsync("request1");
-            fakeCall.SendCompletionHandler(false);
+            fakeCall.SendCompletionCallback.OnSendCompletion(false);
 
             // Until the delayed write completion has been triggered,
             // we still act as if there was an active write.
             Assert.Throws(typeof(InvalidOperationException), () => requestStream.WriteAsync("request2"));
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Internal),
                 null,
                 new Metadata());
@@ -242,7 +242,7 @@
             var resultTask = asyncCall.ClientStreamingCallAsync();
             var requestStream = new ClientRequestStream<string, string>(asyncCall);
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
                 CreateResponsePayload(),
                 new Metadata());
@@ -260,7 +260,7 @@
             var resultTask = asyncCall.ClientStreamingCallAsync();
             var requestStream = new ClientRequestStream<string, string>(asyncCall);
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(new Status(StatusCode.OutOfRange, ""), new Metadata()),
                 CreateResponsePayload(),
                 new Metadata());
@@ -282,9 +282,9 @@
 
             Assert.Throws(typeof(InvalidOperationException), () => requestStream.WriteAsync("request1"));
 
-            fakeCall.SendCompletionHandler(true);
+            fakeCall.SendCompletionCallback.OnSendCompletion(true);
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
                 CreateResponsePayload(),
                 new Metadata());
@@ -298,7 +298,7 @@
             var resultTask = asyncCall.ClientStreamingCallAsync();
             var requestStream = new ClientRequestStream<string, string>(asyncCall);
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 new ClientSideStatus(Status.DefaultSuccess, new Metadata()),
                 CreateResponsePayload(),
                 new Metadata());
@@ -319,7 +319,7 @@
             var writeTask = requestStream.WriteAsync("request1");
             Assert.ThrowsAsync(typeof(TaskCanceledException), async () => await writeTask);
 
-            fakeCall.UnaryResponseClientHandler(true,
+            fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true,
                 CreateClientSideStatus(StatusCode.Cancelled),
                 null,
                 new Metadata());
@@ -342,11 +342,11 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
             var readTask = responseStream.MoveNext();
 
-            fakeCall.ReceivedResponseHeadersHandler(true, new Metadata());
+            fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(true, new Metadata());
             Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
 
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
         }
@@ -359,8 +359,8 @@
             var readTask = responseStream.MoveNext();
 
             // try alternative order of completions
-            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
-            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
         }
@@ -372,8 +372,8 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
             var readTask = responseStream.MoveNext();
 
-            fakeCall.ReceivedMessageHandler(false, null);  // after a failed read, we rely on C core to deliver appropriate status code.
-            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Internal));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, null);  // 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);
         }
@@ -385,18 +385,18 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
 
             var readTask1 = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateResponsePayload());
             Assert.IsTrue(readTask1.Result);
             Assert.AreEqual("response1", responseStream.Current);
 
             var readTask2 = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateResponsePayload());
             Assert.IsTrue(readTask2.Result);
             Assert.AreEqual("response1", responseStream.Current);
 
             var readTask3 = responseStream.MoveNext();
-            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
-            fakeCall.ReceivedMessageHandler(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask3);
         }
@@ -409,12 +409,12 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
 
             var writeTask1 = requestStream.CompleteAsync();
-            fakeCall.SendCompletionHandler(true);
+            fakeCall.SendCompletionCallback.OnSendCompletion(true);
             Assert.DoesNotThrowAsync(async () => await writeTask1);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
         }
@@ -427,8 +427,8 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
 
@@ -445,8 +445,8 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()));
 
             AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask);
 
@@ -461,14 +461,14 @@
             var responseStream = new ClientResponseStream<string, string>(asyncCall);
 
             var writeTask = requestStream.WriteAsync("request1");
-            fakeCall.SendCompletionHandler(false);
+            fakeCall.SendCompletionCallback.OnSendCompletion(false);
 
             // The write will wait for call to finish to receive the status code.
             Assert.IsFalse(writeTask.IsCompleted);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.PermissionDenied));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.PermissionDenied));
 
             var ex = Assert.ThrowsAsync<RpcException>(async () => await writeTask);
             Assert.AreEqual(StatusCode.PermissionDenied, ex.Status.StatusCode);
@@ -486,9 +486,9 @@
             var writeTask = requestStream.WriteAsync("request1");
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.PermissionDenied));
-            fakeCall.SendCompletionHandler(false);
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.PermissionDenied));
+            fakeCall.SendCompletionCallback.OnSendCompletion(false);
 
             var ex = Assert.ThrowsAsync<RpcException>(async () => await writeTask);
             Assert.AreEqual(StatusCode.PermissionDenied, ex.Status.StatusCode);
@@ -510,8 +510,8 @@
             Assert.ThrowsAsync(typeof(TaskCanceledException), async () => await writeTask);
 
             var readTask = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Cancelled));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled));
 
             AssertStreamingResponseError(asyncCall, fakeCall, readTask, StatusCode.Cancelled);
         }
@@ -526,13 +526,13 @@
             Assert.IsTrue(fakeCall.IsCancelled);
 
             var readTask1 = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateResponsePayload());
             Assert.IsTrue(readTask1.Result);
             Assert.AreEqual("response1", responseStream.Current);
 
             var readTask2 = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Cancelled));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled));
 
             AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled);
         }
@@ -547,13 +547,13 @@
             asyncCall.Cancel();
             Assert.IsTrue(fakeCall.IsCancelled);
 
-            fakeCall.ReceivedMessageHandler(true, CreateResponsePayload());
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateResponsePayload());
             Assert.IsTrue(readTask1.Result);
             Assert.AreEqual("response1", responseStream.Current);
 
             var readTask2 = responseStream.MoveNext();
-            fakeCall.ReceivedMessageHandler(true, null);
-            fakeCall.ReceivedStatusOnClientHandler(true, CreateClientSideStatus(StatusCode.Cancelled));
+            fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null);
+            fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled));
 
             AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled);
         }
diff --git a/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs b/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs
index c3a2716..581ac33 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/FakeNativeCall.cs
@@ -31,43 +31,43 @@
     /// </summary>
     internal class FakeNativeCall : INativeCall
     {
-        public UnaryResponseClientHandler UnaryResponseClientHandler
+        public IUnaryResponseClientCallback UnaryResponseClientCallback
         {
             get;
             set;
         }
 
-        public ReceivedStatusOnClientHandler ReceivedStatusOnClientHandler
+        public IReceivedStatusOnClientCallback ReceivedStatusOnClientCallback
         {
             get;
             set;
         }
 
-        public ReceivedMessageHandler ReceivedMessageHandler
+        public IReceivedMessageCallback ReceivedMessageCallback
         {
             get;
             set;
         }
 
-        public ReceivedResponseHeadersHandler ReceivedResponseHeadersHandler
+        public IReceivedResponseHeadersCallback ReceivedResponseHeadersCallback
         {
             get;
             set;
         }
 
-        public SendCompletionHandler SendCompletionHandler
+        public ISendCompletionCallback SendCompletionCallback
         {
             get;
             set;
         }
 
-        public SendCompletionHandler SendStatusFromServerHandler
+        public ISendStatusFromServerCompletionCallback SendStatusFromServerCallback
         {
             get;
             set;
         }
 
-        public ReceivedCloseOnServerHandler ReceivedCloseOnServerHandler
+        public IReceivedCloseOnServerCallback ReceivedCloseOnServerCallback
         {
             get;
             set;
@@ -100,9 +100,9 @@
             return "PEER";
         }
 
-        public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartUnary(IUnaryResponseClientCallback callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
-            UnaryResponseClientHandler = callback;
+            UnaryResponseClientCallback = callback;
         }
 
         public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
@@ -110,55 +110,55 @@
             throw new NotImplementedException();
         }
 
-        public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartClientStreaming(IUnaryResponseClientCallback callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
-            UnaryResponseClientHandler = callback;
+            UnaryResponseClientCallback = callback;
         }
 
-        public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartServerStreaming(IReceivedStatusOnClientCallback callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
-            ReceivedStatusOnClientHandler = callback;
+            ReceivedStatusOnClientCallback = callback;
         }
 
-        public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartDuplexStreaming(IReceivedStatusOnClientCallback callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
-            ReceivedStatusOnClientHandler = callback;
+            ReceivedStatusOnClientCallback = callback;
         }
 
-        public void StartReceiveMessage(ReceivedMessageHandler callback)
+        public void StartReceiveMessage(IReceivedMessageCallback callback)
         {
-            ReceivedMessageHandler = callback;
+            ReceivedMessageCallback = callback;
         }
 
-        public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback)
+        public void StartReceiveInitialMetadata(IReceivedResponseHeadersCallback callback)
         {
-            ReceivedResponseHeadersHandler = callback;
+            ReceivedResponseHeadersCallback = callback;
         }
 
-        public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray)
+        public void StartSendInitialMetadata(ISendCompletionCallback callback, MetadataArraySafeHandle metadataArray)
         {
-            SendCompletionHandler = callback;
+            SendCompletionCallback = callback;
         }
 
-        public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
+        public void StartSendMessage(ISendCompletionCallback callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
         {
-            SendCompletionHandler = callback;
+            SendCompletionCallback = callback;
         }
 
-        public void StartSendCloseFromClient(SendCompletionHandler callback)
+        public void StartSendCloseFromClient(ISendCompletionCallback callback)
         {
-            SendCompletionHandler = callback;
+            SendCompletionCallback = callback;
         }
 
-        public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
+        public void StartSendStatusFromServer(ISendStatusFromServerCompletionCallback callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
             byte[] optionalPayload, WriteFlags writeFlags)
         {
-            SendStatusFromServerHandler = callback;
+            SendStatusFromServerCallback = callback;
         }
 
-        public void StartServerSide(ReceivedCloseOnServerHandler callback)
+        public void StartServerSide(IReceivedCloseOnServerCallback callback)
         {
-            ReceivedCloseOnServerHandler = callback;
+            ReceivedCloseOnServerCallback = callback;
         }
 
         public void Dispose()
diff --git a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
index 7529c44..43f816b 100644
--- a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
+++ b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
@@ -63,7 +63,7 @@
         [Ignore("Prevent running on Jenkins")]
         public void NativeCallbackBenchmark()
         {
-            OpCompletionDelegate handler = Handler;
+            NativeCallbackTestDelegate handler = Handler;
 
             counter = 0;
             BenchmarkUtil.RunBenchmark(
@@ -91,7 +91,7 @@
                 10000, 10000,
                 () =>
                 {
-                    Native.grpcsharp_test_callback(new OpCompletionDelegate(Handler));
+                    Native.grpcsharp_test_callback(new NativeCallbackTestDelegate(Handler));
                 });
             Assert.AreNotEqual(0, counter);
         }
diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs
index 1803920..f9925a8 100644
--- a/src/csharp/Grpc.Core/Channel.cs
+++ b/src/csharp/Grpc.Core/Channel.cs
@@ -127,6 +127,20 @@
             }
         }
 
+        // cached handler for watch connectivity state
+        static readonly BatchCompletionDelegate WatchConnectivityStateHandler = (success, ctx, state) =>
+        {
+            var tcs = (TaskCompletionSource<object>) state;
+            if (success)
+            {
+                tcs.SetResult(null);
+            }
+            else
+            {
+                tcs.SetCanceled();
+            }
+        };
+
         /// <summary>
         /// Returned tasks completes once channel state has become different from 
         /// given lastObservedState. 
@@ -138,18 +152,8 @@
                 "Shutdown is a terminal state. No further state changes can occur.");
             var tcs = new TaskCompletionSource<object>();
             var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture;
-            var handler = new BatchCompletionDelegate((success, ctx) =>
-            {
-                if (success)
-                {
-                    tcs.SetResult(null);
-                }
-                else
-                {
-                    tcs.SetCanceled();
-                }
-            });
-            handle.WatchConnectivityState(lastObservedState, deadlineTimespec, completionQueue, handler);
+            // pass "tcs" as "state" for WatchConnectivityStateHandler.
+            handle.WatchConnectivityState(lastObservedState, deadlineTimespec, completionQueue, WatchConnectivityStateHandler, tcs);
             return tcs.Task;
         }
 
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 09fb722..aa21612 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -27,7 +27,7 @@
     /// <summary>
     /// Manages client side native call lifecycle.
     /// </summary>
-    internal class AsyncCall<TRequest, TResponse> : AsyncCallBase<TRequest, TResponse>
+    internal class AsyncCall<TRequest, TResponse> : AsyncCallBase<TRequest, TResponse>, IUnaryResponseClientCallback, IReceivedStatusOnClientCallback, IReceivedResponseHeadersCallback
     {
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>();
 
@@ -138,7 +138,7 @@
                 unaryResponseTcs = new TaskCompletionSource<TResponse>();
                 using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
-                    call.StartUnary(HandleUnaryResponse, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags);
+                    call.StartUnary(UnaryResponseClientCallback, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags);
                 }
                 return unaryResponseTcs.Task;
             }
@@ -162,7 +162,7 @@
                 unaryResponseTcs = new TaskCompletionSource<TResponse>();
                 using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
-                    call.StartClientStreaming(HandleUnaryResponse, metadataArray, details.Options.Flags);
+                    call.StartClientStreaming(UnaryResponseClientCallback, metadataArray, details.Options.Flags);
                 }
 
                 return unaryResponseTcs.Task;
@@ -188,9 +188,9 @@
                 streamingResponseCallFinishedTcs = new TaskCompletionSource<object>();
                 using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
-                    call.StartServerStreaming(HandleFinished, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags);
+                    call.StartServerStreaming(ReceivedStatusOnClientCallback, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags);
                 }
-                call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders);
+                call.StartReceiveInitialMetadata(ReceivedResponseHeadersCallback);
             }
         }
 
@@ -210,9 +210,9 @@
                 streamingResponseCallFinishedTcs = new TaskCompletionSource<object>();
                 using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
                 {
-                    call.StartDuplexStreaming(HandleFinished, metadataArray, details.Options.Flags);
+                    call.StartDuplexStreaming(ReceivedStatusOnClientCallback, metadataArray, details.Options.Flags);
                 }
-                call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders);
+                call.StartReceiveInitialMetadata(ReceivedResponseHeadersCallback);
             }
         }
 
@@ -256,7 +256,7 @@
                     halfcloseRequested = true;
                     return TaskUtils.CompletedTask;
                 }
-                call.StartSendCloseFromClient(HandleSendFinished);
+                call.StartSendCloseFromClient(SendCompletionCallback);
 
                 halfcloseRequested = true;
                 streamingWriteTcs = new TaskCompletionSource<object>();
@@ -516,5 +516,26 @@
 
             streamingResponseCallFinishedTcs.SetResult(null);
         }
+
+        IUnaryResponseClientCallback UnaryResponseClientCallback => this;
+
+        void IUnaryResponseClientCallback.OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
+        {
+            HandleUnaryResponse(success, receivedStatus, receivedMessage, responseHeaders);
+        }
+
+        IReceivedStatusOnClientCallback ReceivedStatusOnClientCallback => this;
+
+        void IReceivedStatusOnClientCallback.OnReceivedStatusOnClient(bool success, ClientSideStatus receivedStatus)
+        {
+            HandleFinished(success, receivedStatus);
+        }
+
+        IReceivedResponseHeadersCallback ReceivedResponseHeadersCallback => this;
+
+        void IReceivedResponseHeadersCallback.OnReceivedResponseHeaders(bool success, Metadata responseHeaders)
+        {
+            HandleReceivedResponseHeaders(success, responseHeaders);
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index f379c85..3273c26 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -35,7 +35,7 @@
     /// Base for handling both client side and server side calls.
     /// Manages native call lifecycle and provides convenience methods.
     /// </summary>
-    internal abstract class AsyncCallBase<TWrite, TRead>
+    internal abstract class AsyncCallBase<TWrite, TRead> : IReceivedMessageCallback, ISendCompletionCallback
     {
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCallBase<TWrite, TRead>>();
         protected static readonly Status DeserializeResponseFailureStatus = new Status(StatusCode.Internal, "Failed to deserialize response message.");
@@ -126,7 +126,7 @@
                     return earlyResult;
                 }
 
-                call.StartSendMessage(HandleSendFinished, payload, writeFlags, !initialMetadataSent);
+                call.StartSendMessage(SendCompletionCallback, payload, writeFlags, !initialMetadataSent);
 
                 initialMetadataSent = true;
                 streamingWritesCounter++;
@@ -154,7 +154,7 @@
                 GrpcPreconditions.CheckState(streamingReadTcs == null, "Only one read can be pending at a time");
                 GrpcPreconditions.CheckState(!disposed);
 
-                call.StartReceiveMessage(HandleReadFinished);
+                call.StartReceiveMessage(ReceivedMessageCallback);
                 streamingReadTcs = new TaskCompletionSource<TRead>();
                 return streamingReadTcs.Task;
             }
@@ -342,5 +342,19 @@
             }
             origTcs.SetResult(msg);
         }
+
+        protected ISendCompletionCallback SendCompletionCallback => this;
+
+        void ISendCompletionCallback.OnSendCompletion(bool success)
+        {
+            HandleSendFinished(success);
+        }
+
+        IReceivedMessageCallback ReceivedMessageCallback => this;
+
+        void IReceivedMessageCallback.OnReceivedMessage(bool success, byte[] receivedMessage)
+        {
+            HandleReadFinished(success, receivedMessage);
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
index 271a6ff..11acb27 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
@@ -31,7 +31,7 @@
     /// <summary>
     /// Manages server side native call lifecycle.
     /// </summary>
-    internal class AsyncCallServer<TRequest, TResponse> : AsyncCallBase<TResponse, TRequest>
+    internal class AsyncCallServer<TRequest, TResponse> : AsyncCallBase<TResponse, TRequest>, IReceivedCloseOnServerCallback, ISendStatusFromServerCompletionCallback
     {
         readonly TaskCompletionSource<object> finishedServersideTcs = new TaskCompletionSource<object>();
         readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
@@ -70,7 +70,7 @@
 
                 started = true;
 
-                call.StartServerSide(HandleFinishedServerside);
+                call.StartServerSide(ReceiveCloseOnServerCallback);
                 return finishedServersideTcs.Task;
             }
         }
@@ -114,7 +114,7 @@
 
                 using (var metadataArray = MetadataArraySafeHandle.Create(headers))
                 {
-                    call.StartSendInitialMetadata(HandleSendFinished, metadataArray);
+                    call.StartSendInitialMetadata(SendCompletionCallback, metadataArray);
                 }
 
                 this.initialMetadataSent = true;
@@ -127,10 +127,10 @@
         /// Sends call result status, indicating we are done with writes.
         /// Sending a status different from StatusCode.OK will also implicitly cancel the call.
         /// </summary>
-        public Task SendStatusFromServerAsync(Status status, Metadata trailers, Tuple<TResponse, WriteFlags> optionalWrite)
+        public Task SendStatusFromServerAsync(Status status, Metadata trailers, ResponseWithFlags? optionalWrite)
         {
-            byte[] payload = optionalWrite != null ? UnsafeSerialize(optionalWrite.Item1) : null;
-            var writeFlags = optionalWrite != null ? optionalWrite.Item2 : default(WriteFlags);
+            byte[] payload = optionalWrite.HasValue ? UnsafeSerialize(optionalWrite.Value.Response) : null;
+            var writeFlags = optionalWrite.HasValue ? optionalWrite.Value.WriteFlags : default(WriteFlags);
 
             lock (myLock)
             {
@@ -140,13 +140,13 @@
 
                 using (var metadataArray = MetadataArraySafeHandle.Create(trailers))
                 {
-                    call.StartSendStatusFromServer(HandleSendStatusFromServerFinished, status, metadataArray, !initialMetadataSent,
+                    call.StartSendStatusFromServer(SendStatusFromServerCompletionCallback, status, metadataArray, !initialMetadataSent,
                         payload, writeFlags);
                 }
                 halfcloseRequested = true;
                 initialMetadataSent = true;
                 sendStatusFromServerTcs = new TaskCompletionSource<object>();
-                if (optionalWrite != null)
+                if (optionalWrite.HasValue)
                 {
                     streamingWritesCounter++;
                 }
@@ -227,5 +227,31 @@
 
             finishedServersideTcs.SetResult(null);
         }
+
+        IReceivedCloseOnServerCallback ReceiveCloseOnServerCallback => this;
+
+        void IReceivedCloseOnServerCallback.OnReceivedCloseOnServer(bool success, bool cancelled)
+        {
+            HandleFinishedServerside(success, cancelled);
+        }
+
+        ISendStatusFromServerCompletionCallback SendStatusFromServerCompletionCallback => this;
+
+        void ISendStatusFromServerCompletionCallback.OnSendStatusFromServerCompletion(bool success)
+        {
+            HandleSendStatusFromServerFinished(success);
+        }
+
+        public struct ResponseWithFlags
+        {
+            public ResponseWithFlags(TResponse response, WriteFlags writeFlags)
+            {
+                this.Response = response;
+                this.WriteFlags = writeFlags;
+            }
+
+            public TResponse Response { get; }
+            public WriteFlags WriteFlags { get; }
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
index cd5e3d8..1e6f1fb 100644
--- a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
@@ -20,15 +20,25 @@
 using System.Runtime.InteropServices;
 using System.Text;
 using Grpc.Core;
+using Grpc.Core.Logging;
+using Grpc.Core.Utils;
 
 namespace Grpc.Core.Internal
 {
+    internal interface IOpCompletionCallback
+    {
+        void OnComplete(bool success);
+    }
+
     /// <summary>
     /// grpcsharp_batch_context
     /// </summary>
-    internal class BatchContextSafeHandle : SafeHandleZeroIsInvalid
+    internal class BatchContextSafeHandle : SafeHandleZeroIsInvalid, IOpCompletionCallback
     {
         static readonly NativeMethods Native = NativeMethods.Get();
+        static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<BatchContextSafeHandle>();
+
+        CompletionCallbackData completionCallbackData;
 
         private BatchContextSafeHandle()
         {
@@ -47,19 +57,26 @@
             }
         }
 
+        public void SetCompletionCallback(BatchCompletionDelegate callback, object state)
+        {
+            GrpcPreconditions.CheckState(completionCallbackData.Callback == null);
+            GrpcPreconditions.CheckNotNull(callback, nameof(callback));
+            completionCallbackData = new CompletionCallbackData(callback, state);
+        }
+
         // Gets data of recv_initial_metadata completion.
         public Metadata GetReceivedInitialMetadata()
         {
             IntPtr metadataArrayPtr = Native.grpcsharp_batch_context_recv_initial_metadata(this);
             return MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr);
         }
-            
+
         // Gets data of recv_status_on_client completion.
         public ClientSideStatus GetReceivedStatusOnClient()
         {
             UIntPtr detailsLength;
             IntPtr detailsPtr = Native.grpcsharp_batch_context_recv_status_on_client_details(this, out detailsLength);
-            string details = MarshalUtils.PtrToStringUTF8(detailsPtr, (int) detailsLength.ToUInt32());
+            string details = MarshalUtils.PtrToStringUTF8(detailsPtr, (int)detailsLength.ToUInt32());
             var status = new Status(Native.grpcsharp_batch_context_recv_status_on_client_status(this), details);
 
             IntPtr metadataArrayPtr = Native.grpcsharp_batch_context_recv_status_on_client_trailing_metadata(this);
@@ -86,11 +103,40 @@
         {
             return Native.grpcsharp_batch_context_recv_close_on_server_cancelled(this) != 0;
         }
-            
+
         protected override bool ReleaseHandle()
         {
             Native.grpcsharp_batch_context_destroy(handle);
             return true;
         }
+
+        void IOpCompletionCallback.OnComplete(bool success)
+        {
+            try
+            {
+                completionCallbackData.Callback(success, this, completionCallbackData.State);
+            }
+            catch (Exception e)
+            {
+                Logger.Error(e, "Exception occured while invoking batch completion delegate.");
+            }
+            finally
+            {
+                completionCallbackData = default(CompletionCallbackData);
+                Dispose();
+            }
+        }
+
+        struct CompletionCallbackData
+        {
+            public CompletionCallbackData(BatchCompletionDelegate callback, object state)
+            {
+                this.Callback = callback;
+                this.State = state;
+            }
+
+            public BatchCompletionDelegate Callback { get; }
+            public object State { get; }
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index 3a7f977..d6a5ba5 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -32,6 +32,23 @@
         public static readonly CallSafeHandle NullInstance = new CallSafeHandle();
         static readonly NativeMethods Native = NativeMethods.Get();
 
+        // 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());
+        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());
+        static readonly BatchCompletionDelegate CompletionHandler_IReceivedResponseHeadersCallback =
+            (success, context, state) => ((IReceivedResponseHeadersCallback)state).OnReceivedResponseHeaders(success, context.GetReceivedInitialMetadata());
+        static readonly BatchCompletionDelegate CompletionHandler_ISendCompletionCallback =
+            (success, context, state) => ((ISendCompletionCallback)state).OnSendCompletion(success);
+        static readonly BatchCompletionDelegate CompletionHandler_ISendStatusFromServerCompletionCallback =
+            (success, context, state) => ((ISendStatusFromServerCompletionCallback)state).OnSendStatusFromServerCompletion(success);
+        static readonly BatchCompletionDelegate CompletionHandler_IReceivedCloseOnServerCallback =
+            (success, context, state) => ((IReceivedCloseOnServerCallback)state).OnReceivedCloseOnServer(success, context.GetReceivedCloseOnServerCancelled());
+
         const uint GRPC_WRITE_BUFFER_HINT = 1;
         CompletionQueueSafeHandle completionQueue;
 
@@ -49,12 +66,12 @@
             Native.grpcsharp_call_set_credentials(this, credentials).CheckOk();
         }
 
-        public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartUnary(IUnaryResponseClientCallback callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata()));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_IUnaryResponseClientCallback, callback);
                 Native.grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, metadataArray, callFlags)
                     .CheckOk();
             }
@@ -66,106 +83,106 @@
                 .CheckOk();
         }
 
-        public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartClientStreaming(IUnaryResponseClientCallback callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata()));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_IUnaryResponseClientCallback, callback);
                 Native.grpcsharp_call_start_client_streaming(this, ctx, metadataArray, callFlags).CheckOk();
             }
         }
 
-        public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartServerStreaming(IReceivedStatusOnClientCallback callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient()));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_IReceivedStatusOnClientCallback, callback);
                 Native.grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, metadataArray, callFlags).CheckOk();
             }
         }
 
-        public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
+        public void StartDuplexStreaming(IReceivedStatusOnClientCallback callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient()));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_IReceivedStatusOnClientCallback, callback);
                 Native.grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray, callFlags).CheckOk();
             }
         }
 
-        public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
+        public void StartSendMessage(ISendCompletionCallback callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_ISendCompletionCallback, callback);
                 Native.grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, sendEmptyInitialMetadata ? 1 : 0).CheckOk();
             }
         }
 
-        public void StartSendCloseFromClient(SendCompletionHandler callback)
+        public void StartSendCloseFromClient(ISendCompletionCallback callback)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_ISendCompletionCallback, callback);
                 Native.grpcsharp_call_send_close_from_client(this, ctx).CheckOk();
             }
         }
 
-        public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
+        public void StartSendStatusFromServer(ISendStatusFromServerCompletionCallback callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata,
             byte[] optionalPayload, WriteFlags writeFlags)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
                 var optionalPayloadLength = optionalPayload != null ? new UIntPtr((ulong)optionalPayload.Length) : UIntPtr.Zero;
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_ISendStatusFromServerCompletionCallback, callback);
                 var statusDetailBytes = MarshalUtils.GetBytesUTF8(status.Detail);
                 Native.grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, statusDetailBytes, new UIntPtr((ulong)statusDetailBytes.Length), metadataArray, sendEmptyInitialMetadata ? 1 : 0,
                     optionalPayload, optionalPayloadLength, writeFlags).CheckOk();
             }
         }
 
-        public void StartReceiveMessage(ReceivedMessageHandler callback)
+        public void StartReceiveMessage(IReceivedMessageCallback callback)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedMessage()));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_IReceivedMessageCallback, callback);
                 Native.grpcsharp_call_recv_message(this, ctx).CheckOk();
             }
         }
 
-        public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback)
+        public void StartReceiveInitialMetadata(IReceivedResponseHeadersCallback callback)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedInitialMetadata()));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_IReceivedResponseHeadersCallback, callback);
                 Native.grpcsharp_call_recv_initial_metadata(this, ctx).CheckOk();
             }
         }
 
-        public void StartServerSide(ReceivedCloseOnServerHandler callback)
+        public void StartServerSide(IReceivedCloseOnServerCallback callback)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedCloseOnServerCancelled()));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_IReceivedCloseOnServerCallback, callback);
                 Native.grpcsharp_call_start_serverside(this, ctx).CheckOk();
             }
         }
 
-        public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray)
+        public void StartSendInitialMetadata(ISendCompletionCallback callback, MetadataArraySafeHandle metadataArray)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, CompletionHandler_ISendCompletionCallback, callback);
                 Native.grpcsharp_call_send_initial_metadata(this, ctx, metadataArray).CheckOk();
             }
         }
diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
index f826a17..1eeb0e3 100644
--- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
@@ -64,10 +64,10 @@
             return Native.grpcsharp_channel_check_connectivity_state(this, tryToConnect ? 1 : 0);
         }
 
-        public void WatchConnectivityState(ChannelState lastObservedState, Timespec deadline, CompletionQueueSafeHandle cq, BatchCompletionDelegate callback)
+        public void WatchConnectivityState(ChannelState lastObservedState, Timespec deadline, CompletionQueueSafeHandle cq, BatchCompletionDelegate callback, object callbackState)
         {
             var ctx = BatchContextSafeHandle.Create();
-            cq.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
+            cq.CompletionRegistry.RegisterBatchCompletion(ctx, callback, callbackState);
             Native.grpcsharp_channel_watch_connectivity_state(this, lastObservedState, deadline, cq, ctx);
         }
 
diff --git a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
index 1102c8d..b68655b 100644
--- a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
+++ b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
@@ -19,15 +19,15 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
+using System.Threading;
 using Grpc.Core.Logging;
 using Grpc.Core.Utils;
 
 namespace Grpc.Core.Internal
 {
-    internal delegate void OpCompletionDelegate(bool success);
-
-    internal delegate void BatchCompletionDelegate(bool success, BatchContextSafeHandle ctx);
+    internal delegate void BatchCompletionDelegate(bool success, BatchContextSafeHandle ctx, object state);
 
     internal delegate void RequestCallCompletionDelegate(bool success, RequestCallContextSafeHandle ctx);
 
@@ -36,8 +36,8 @@
         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<CompletionRegistry>();
 
         readonly GrpcEnvironment environment;
-        readonly Dictionary<IntPtr, OpCompletionDelegate> dict = new Dictionary<IntPtr, OpCompletionDelegate>(new IntPtrComparer());
-        readonly object myLock = new object();
+        readonly Dictionary<IntPtr, IOpCompletionCallback> dict = new Dictionary<IntPtr, IOpCompletionCallback>(new IntPtrComparer());
+        SpinLock spinLock = new SpinLock(Debugger.IsAttached);
         IntPtr lastRegisteredKey;  // only for testing
 
         public CompletionRegistry(GrpcEnvironment environment)
@@ -45,38 +45,51 @@
             this.environment = environment;
         }
 
-        public void Register(IntPtr key, OpCompletionDelegate callback)
+        public void Register(IntPtr key, IOpCompletionCallback callback)
         {
             environment.DebugStats.PendingBatchCompletions.Increment();
-            lock (myLock)
+
+            bool lockTaken = false;
+            try
             {
+                spinLock.Enter(ref lockTaken);
+
                 dict.Add(key, callback);
                 this.lastRegisteredKey = key;
             }
+            finally
+            {
+                if (lockTaken) spinLock.Exit();
+            }
         }
 
-        public void RegisterBatchCompletion(BatchContextSafeHandle ctx, BatchCompletionDelegate callback)
+        public void RegisterBatchCompletion(BatchContextSafeHandle ctx, BatchCompletionDelegate callback, object state)
         {
-            // TODO(jtattermusch): get rid of new delegate creation here
-            OpCompletionDelegate opCallback = ((success) => HandleBatchCompletion(success, ctx, callback));
-            Register(ctx.Handle, opCallback);
+            ctx.SetCompletionCallback(callback, state);
+            Register(ctx.Handle, ctx);
         }
 
         public void RegisterRequestCallCompletion(RequestCallContextSafeHandle ctx, RequestCallCompletionDelegate callback)
         {
-            // TODO(jtattermusch): get rid of new delegate creation here
-            OpCompletionDelegate opCallback = ((success) => HandleRequestCallCompletion(success, ctx, callback));
-            Register(ctx.Handle, opCallback);
+            ctx.CompletionCallback = callback;
+            Register(ctx.Handle, ctx);
         }
 
-        public OpCompletionDelegate Extract(IntPtr key)
+        public IOpCompletionCallback Extract(IntPtr key)
         {
-            OpCompletionDelegate value = null;
-            lock (myLock)
+            IOpCompletionCallback value = null;
+            bool lockTaken = false;
+            try
             {
+                spinLock.Enter(ref lockTaken);
+
                 value = dict[key];
                 dict.Remove(key);
             }
+            finally
+            {
+                if (lockTaken) spinLock.Exit();
+            }
             environment.DebugStats.PendingBatchCompletions.Decrement();
             return value;
         }
@@ -89,44 +102,6 @@
             get { return this.lastRegisteredKey; }
         }
 
-        private static void HandleBatchCompletion(bool success, BatchContextSafeHandle ctx, BatchCompletionDelegate callback)
-        {
-            try
-            {
-                callback(success, ctx);
-            }
-            catch (Exception e)
-            {
-                Logger.Error(e, "Exception occured while invoking batch completion delegate.");
-            }
-            finally
-            {
-                if (ctx != null)
-                {
-                    ctx.Dispose();
-                }
-            }
-        }
-
-        private static void HandleRequestCallCompletion(bool success, RequestCallContextSafeHandle ctx, RequestCallCompletionDelegate callback)
-        {
-            try
-            {
-                callback(success, ctx);
-            }
-            catch (Exception e)
-            {
-                Logger.Error(e, "Exception occured while invoking request call completion delegate.");
-            }
-            finally
-            {
-                if (ctx != null)
-                {
-                    ctx.Dispose();
-                }
-            }
-        }
-
         /// <summary>
         /// IntPtr doesn't implement <c>IEquatable{IntPtr}</c> so we need to use custom comparer to avoid boxing.
         /// </summary>
diff --git a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
index f7f723c..bd0229a 100644
--- a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
+++ b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
@@ -68,8 +68,8 @@
             GrpcPreconditions.CheckArgument(poolSize >= completionQueueCount,
                 "Thread pool size cannot be smaller than the number of completion queues used.");
 
-            this.runCompletionQueueEventCallbackSuccess = new WaitCallback((callback) => RunCompletionQueueEventCallback((OpCompletionDelegate) callback, true));
-            this.runCompletionQueueEventCallbackFailure = new WaitCallback((callback) => RunCompletionQueueEventCallback((OpCompletionDelegate) callback, false));
+            this.runCompletionQueueEventCallbackSuccess = new WaitCallback((callback) => RunCompletionQueueEventCallback((IOpCompletionCallback) callback, true));
+            this.runCompletionQueueEventCallbackFailure = new WaitCallback((callback) => RunCompletionQueueEventCallback((IOpCompletionCallback) callback, false));
         }
 
         public void Start()
@@ -225,11 +225,11 @@
             return list.AsReadOnly();
         }
 
-        private void RunCompletionQueueEventCallback(OpCompletionDelegate callback, bool success)
+        private void RunCompletionQueueEventCallback(IOpCompletionCallback callback, bool success)
         {
             try
             {
-                callback(success);
+                callback.OnComplete(success);
             }
             catch (Exception e)
             {
diff --git a/src/csharp/Grpc.Core/Internal/INativeCall.cs b/src/csharp/Grpc.Core/Internal/INativeCall.cs
index f9c0658..5c35b2b 100644
--- a/src/csharp/Grpc.Core/Internal/INativeCall.cs
+++ b/src/csharp/Grpc.Core/Internal/INativeCall.cs
@@ -20,18 +20,41 @@
 
 namespace Grpc.Core.Internal
 {
-    internal delegate void UnaryResponseClientHandler(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders);
+    internal interface IUnaryResponseClientCallback
+    {
+        void OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders);
+    }
 
     // Received status for streaming response calls.
-    internal delegate void ReceivedStatusOnClientHandler(bool success, ClientSideStatus receivedStatus);
+    internal interface IReceivedStatusOnClientCallback
+    {
+        void OnReceivedStatusOnClient(bool success, ClientSideStatus receivedStatus);
+    }
 
-    internal delegate void ReceivedMessageHandler(bool success, byte[] receivedMessage);
+    internal interface IReceivedMessageCallback
+    {
+        void OnReceivedMessage(bool success, byte[] receivedMessage);
+    }
 
-    internal delegate void ReceivedResponseHeadersHandler(bool success, Metadata responseHeaders);
+    internal interface IReceivedResponseHeadersCallback
+    {
+        void OnReceivedResponseHeaders(bool success, Metadata responseHeaders);
+    }
 
-    internal delegate void SendCompletionHandler(bool success);
+    internal interface ISendCompletionCallback
+    {
+        void OnSendCompletion(bool success);
+    }
 
-    internal delegate void ReceivedCloseOnServerHandler(bool success, bool cancelled);
+    internal interface ISendStatusFromServerCompletionCallback
+    {
+        void OnSendStatusFromServerCompletion(bool success);
+    }
+
+    internal interface IReceivedCloseOnServerCallback
+    {
+        void OnReceivedCloseOnServer(bool success, bool cancelled);
+    }
 
     /// <summary>
     /// Abstraction of a native call object.
@@ -44,28 +67,28 @@
 
         string GetPeer();
 
-        void StartUnary(UnaryResponseClientHandler callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
+        void StartUnary(IUnaryResponseClientCallback callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
 
         void StartUnary(BatchContextSafeHandle ctx, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
 
-        void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
+        void StartClientStreaming(IUnaryResponseClientCallback callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
 
-        void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
+        void StartServerStreaming(IReceivedStatusOnClientCallback callback, byte[] payload, WriteFlags writeFlags, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
 
-        void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
+        void StartDuplexStreaming(IReceivedStatusOnClientCallback callback, MetadataArraySafeHandle metadataArray, CallFlags callFlags);
 
-        void StartReceiveMessage(ReceivedMessageHandler callback);
+        void StartReceiveMessage(IReceivedMessageCallback callback);
 
-        void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback);
+        void StartReceiveInitialMetadata(IReceivedResponseHeadersCallback callback);
 
-        void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray);
+        void StartSendInitialMetadata(ISendCompletionCallback callback, MetadataArraySafeHandle metadataArray);
 
-        void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
+        void StartSendMessage(ISendCompletionCallback callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
 
-        void StartSendCloseFromClient(SendCompletionHandler callback);
+        void StartSendCloseFromClient(ISendCompletionCallback callback);
 
-        void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata, byte[] optionalPayload, WriteFlags writeFlags);
+        void StartSendStatusFromServer(ISendStatusFromServerCompletionCallback callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata, byte[] optionalPayload, WriteFlags writeFlags);
 
-        void StartServerSide(ReceivedCloseOnServerHandler callback);
+        void StartServerSide(IReceivedCloseOnServerCallback callback);
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
index 22faa19..d517252 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMethods.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
@@ -29,6 +29,8 @@
 
 namespace Grpc.Core.Internal
 {
+    internal delegate void NativeCallbackTestDelegate(bool success);
+
     /// <summary>
     /// Provides access to all native methods provided by <c>NativeExtension</c>.
     /// An extra level of indirection is added to P/Invoke calls to allow intelligent loading
@@ -420,7 +422,7 @@
             public delegate Timespec gprsharp_convert_clock_type_delegate(Timespec t, ClockType targetClock);
             public delegate int gprsharp_sizeof_timespec_delegate();
 
-            public delegate CallError grpcsharp_test_callback_delegate([MarshalAs(UnmanagedType.FunctionPtr)] OpCompletionDelegate callback);
+            public delegate CallError grpcsharp_test_callback_delegate([MarshalAs(UnmanagedType.FunctionPtr)] NativeCallbackTestDelegate callback);
             public delegate IntPtr grpcsharp_test_nop_delegate(IntPtr ptr);
             public delegate void grpcsharp_test_override_method_delegate(string methodName, string variant);
         }
diff --git a/src/csharp/Grpc.Core/Internal/RequestCallContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/RequestCallContextSafeHandle.cs
index b7af0c1..09f5c3e 100644
--- a/src/csharp/Grpc.Core/Internal/RequestCallContextSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/RequestCallContextSafeHandle.cs
@@ -19,15 +19,17 @@
 using System;
 using System.Runtime.InteropServices;
 using Grpc.Core;
+using Grpc.Core.Logging;
 
 namespace Grpc.Core.Internal
 {
     /// <summary>
     /// grpcsharp_request_call_context
     /// </summary>
-    internal class RequestCallContextSafeHandle : SafeHandleZeroIsInvalid
+    internal class RequestCallContextSafeHandle : SafeHandleZeroIsInvalid, IOpCompletionCallback
     {
         static readonly NativeMethods Native = NativeMethods.Get();
+        static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<RequestCallContextSafeHandle>();
 
         private RequestCallContextSafeHandle()
         {
@@ -46,6 +48,8 @@
             }
         }
 
+        public RequestCallCompletionDelegate CompletionCallback { get; set; }
+
         // Gets data of server_rpc_new completion.
         public ServerRpcNew GetServerRpcNew(Server server)
         {
@@ -72,5 +76,22 @@
             Native.grpcsharp_request_call_context_destroy(handle);
             return true;
         }
+
+        void IOpCompletionCallback.OnComplete(bool success)
+        {
+            try
+            {
+                CompletionCallback(success, this);
+            }
+            catch (Exception e)
+            {
+                Logger.Error(e, "Exception occured while invoking request call completion delegate.");
+            }
+            finally
+            {
+                CompletionCallback = null;
+                Dispose();
+            }
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 6019f8e..98995a0 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -60,7 +60,7 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
-            Tuple<TResponse,WriteFlags> responseTuple = null;
+            AsyncCallServer<TRequest,TResponse>.ResponseWithFlags? responseWithFlags = null;
             var context = HandlerUtils.NewContext(newRpc, responseStream, asyncCall.CancellationToken);
             try
             {
@@ -68,7 +68,7 @@
                 var request = requestStream.Current;
                 var response = await handler(request, context).ConfigureAwait(false);
                 status = context.Status;
-                responseTuple = Tuple.Create(response, HandlerUtils.GetWriteFlags(context.WriteOptions));
+                responseWithFlags = new AsyncCallServer<TRequest, TResponse>.ResponseWithFlags(response, HandlerUtils.GetWriteFlags(context.WriteOptions));
             } 
             catch (Exception e)
             {
@@ -80,7 +80,7 @@
             }
             try
             {
-                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, responseTuple).ConfigureAwait(false);
+                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, responseWithFlags).ConfigureAwait(false);
             }
             catch (Exception)
             {
@@ -177,13 +177,13 @@
             var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
 
             Status status;
-            Tuple<TResponse,WriteFlags> responseTuple = null;
+            AsyncCallServer<TRequest, TResponse>.ResponseWithFlags? responseWithFlags = null;
             var context = HandlerUtils.NewContext(newRpc, responseStream, asyncCall.CancellationToken);
             try
             {
                 var response = await handler(requestStream, context).ConfigureAwait(false);
                 status = context.Status;
-                responseTuple = Tuple.Create(response, HandlerUtils.GetWriteFlags(context.WriteOptions));
+                responseWithFlags = new AsyncCallServer<TRequest, TResponse>.ResponseWithFlags(response, HandlerUtils.GetWriteFlags(context.WriteOptions));
             }
             catch (Exception e)
             {
@@ -196,7 +196,7 @@
 
             try
             {
-                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, responseTuple).ConfigureAwait(false);
+                await asyncCall.SendStatusFromServerAsync(status, context.ResponseTrailers, responseWithFlags).ConfigureAwait(false);
             }
             catch (Exception)
             {
diff --git a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
index 63000e9..a308890 100644
--- a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
@@ -59,13 +59,15 @@
         {
             Native.grpcsharp_server_start(this);
         }
-    
+
         public void ShutdownAndNotify(BatchCompletionDelegate callback, CompletionQueueSafeHandle completionQueue)
         {
             using (completionQueue.NewScope())
             {
                 var ctx = BatchContextSafeHandle.Create();
-                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
+                // TODO(jtattermusch): delegate allocation by caller can be avoided by utilizing the "state" object,
+                // but server shutdown isn't worth optimizing right now.
+                completionQueue.CompletionRegistry.RegisterBatchCompletion(ctx, callback, null);
                 Native.grpcsharp_server_shutdown_and_notify_callback(this, completionQueue, ctx);
             }
         }
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index 77ad876..71c7f10 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -387,7 +387,7 @@
         /// <summary>
         /// Handles native callback.
         /// </summary>
-        private void HandleServerShutdown(bool success, BatchContextSafeHandle ctx)
+        private void HandleServerShutdown(bool success, BatchContextSafeHandle ctx, object state)
         {
             shutdownTcs.SetResult(null);
         }
diff --git a/src/csharp/Grpc.Microbenchmarks/CompletionRegistryBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/CompletionRegistryBenchmark.cs
new file mode 100644
index 0000000..2d1c33e
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/CompletionRegistryBenchmark.cs
@@ -0,0 +1,78 @@
+#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.Runtime.InteropServices;
+using System.Threading;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Grpc.Microbenchmarks
+{
+    public class CompletionRegistryBenchmark
+    {
+        GrpcEnvironment environment;
+
+        public void Init()
+        {
+            environment = GrpcEnvironment.AddRef();
+        }
+
+        public void Cleanup()
+        {
+            GrpcEnvironment.ReleaseAsync().Wait();
+        }
+
+        public void Run(int threadCount, int iterations, bool useSharedRegistry)
+        {
+            Console.WriteLine(string.Format("CompletionRegistryBenchmark: threads={0}, iterations={1}, useSharedRegistry={2}", threadCount, iterations, useSharedRegistry));
+            CompletionRegistry sharedRegistry = useSharedRegistry ? new CompletionRegistry(environment) : null;
+            var threadedBenchmark = new ThreadedBenchmark(threadCount, () => ThreadBody(iterations, sharedRegistry));
+            threadedBenchmark.Run();
+            // TODO: parametrize by number of pending completions
+        }
+
+        private void ThreadBody(int iterations, CompletionRegistry optionalSharedRegistry)
+        {
+            var completionRegistry = optionalSharedRegistry ?? new CompletionRegistry(environment);
+            var ctx = BatchContextSafeHandle.Create();
+  
+            var stopwatch = Stopwatch.StartNew();
+            for (int i = 0; i < iterations; i++)
+            {
+                completionRegistry.Register(ctx.Handle, ctx);
+                var callback = completionRegistry.Extract(ctx.Handle);
+                // NOTE: we are not calling the callback to avoid disposing ctx.
+            }
+            stopwatch.Stop();
+            Console.WriteLine("Elapsed millis: " + stopwatch.ElapsedMilliseconds);          
+
+            ctx.Dispose();
+        }
+
+        private class NopCompletionCallback : IOpCompletionCallback
+        {
+            public void OnComplete(bool success)
+            {
+
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Microbenchmarks/GCStats.cs b/src/csharp/Grpc.Microbenchmarks/GCStats.cs
new file mode 100644
index 0000000..ca7051e
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/GCStats.cs
@@ -0,0 +1,69 @@
+#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 Grpc.Core;
+using Grpc.Core.Internal;
+
+namespace Grpc.Microbenchmarks
+{
+    internal class GCStats
+    {
+        readonly object myLock = new object();
+        GCStatsSnapshot lastSnapshot;
+
+        public GCStats()
+        {
+            lastSnapshot = new GCStatsSnapshot(GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
+        }
+
+        public GCStatsSnapshot GetSnapshot(bool reset = false)
+        {
+            lock (myLock)
+            {
+                var newSnapshot = new GCStatsSnapshot(GC.CollectionCount(0) - lastSnapshot.Gen0,
+                    GC.CollectionCount(1) - lastSnapshot.Gen1,
+                    GC.CollectionCount(2) - lastSnapshot.Gen2);
+                if (reset)
+                {
+                    lastSnapshot = newSnapshot;
+                }
+                return newSnapshot;
+            }
+        }
+    }
+
+    public class GCStatsSnapshot
+    {
+        public GCStatsSnapshot(int gen0, int gen1, int gen2)
+        {
+            this.Gen0 = gen0;
+            this.Gen1 = gen1;
+            this.Gen2 = gen2;
+        }
+
+        public int Gen0 { get; }
+        public int Gen1 { get; }
+        public int Gen2 { get; }
+
+        public override string ToString()
+        {
+            return string.Format("[GCCollectionCount: gen0 {0}, gen1 {1}, gen2 {2}]", Gen0, Gen1, Gen2);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj b/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
index 108357e..8a629f9 100644
--- a/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
+++ b/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
@@ -15,6 +15,10 @@
     <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
+  </ItemGroup>
+
   <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
     <Reference Include="System" />
     <Reference Include="Microsoft.CSharp" />
diff --git a/src/csharp/Grpc.Microbenchmarks/PInvokeByteArrayBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/PInvokeByteArrayBenchmark.cs
new file mode 100644
index 0000000..787b550
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/PInvokeByteArrayBenchmark.cs
@@ -0,0 +1,64 @@
+#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.Runtime.InteropServices;
+using System.Threading;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Grpc.Microbenchmarks
+{
+    public class PInvokeByteArrayBenchmark
+    {
+        static readonly NativeMethods Native = NativeMethods.Get();
+
+        public void Init()
+        {
+        }
+
+        public void Cleanup()
+        {
+        }
+
+        public void Run(int threadCount, int iterations, int payloadSize)
+        {
+            Console.WriteLine(string.Format("PInvokeByteArrayBenchmark: threads={0}, iterations={1}, payloadSize={2}", threadCount, iterations, payloadSize));
+            var threadedBenchmark = new ThreadedBenchmark(threadCount, () => ThreadBody(iterations, payloadSize));
+            threadedBenchmark.Run();
+        }
+
+        private void ThreadBody(int iterations, int payloadSize)
+        {
+            var payload = new byte[payloadSize];
+         
+            var stopwatch = Stopwatch.StartNew();
+            for (int i = 0; i < iterations; i++)
+            {
+                var gcHandle = GCHandle.Alloc(payload, GCHandleType.Pinned);
+                var payloadPtr = gcHandle.AddrOfPinnedObject();
+                Native.grpcsharp_test_nop(payloadPtr);
+                gcHandle.Free();
+            }
+            stopwatch.Stop();
+            Console.WriteLine("Elapsed millis: " + stopwatch.ElapsedMilliseconds);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Microbenchmarks/Program.cs b/src/csharp/Grpc.Microbenchmarks/Program.cs
index d07d418..a64c297 100644
--- a/src/csharp/Grpc.Microbenchmarks/Program.cs
+++ b/src/csharp/Grpc.Microbenchmarks/Program.cs
@@ -20,14 +20,84 @@
 using Grpc.Core;
 using Grpc.Core.Internal;
 using Grpc.Core.Logging;
+using CommandLine;
+using CommandLine.Text;
 
 namespace Grpc.Microbenchmarks
 {
     class Program
     {
+        public enum MicrobenchmarkType
+        {
+            CompletionRegistry,
+            PInvokeByteArray,
+            SendMessage
+        }
+
+        private class BenchmarkOptions
+        {
+            [Option("benchmark", Required = true, HelpText = "Benchmark to run")]
+            public MicrobenchmarkType Benchmark { get; set; }
+        }
+
         public static void Main(string[] args)
         {
             GrpcEnvironment.SetLogger(new ConsoleLogger());
+            var parserResult = Parser.Default.ParseArguments<BenchmarkOptions>(args)
+                .WithNotParsed(errors => {
+                    Console.WriteLine("Supported benchmarks:");
+                    foreach (var enumValue in Enum.GetValues(typeof(MicrobenchmarkType)))
+                    {
+                        Console.WriteLine("  " + enumValue);
+                    }
+                    Environment.Exit(1);
+                })
+                .WithParsed(options =>
+                {
+                    switch (options.Benchmark)
+                    {
+                        case MicrobenchmarkType.CompletionRegistry:
+                          RunCompletionRegistryBenchmark();
+                          break;
+                        case MicrobenchmarkType.PInvokeByteArray:
+                          RunPInvokeByteArrayBenchmark();
+                          break;
+                        case MicrobenchmarkType.SendMessage:
+                          RunSendMessageBenchmark();
+                          break;
+                        default:
+                          throw new ArgumentException("Unsupported benchmark.");
+                    }
+                });
+        }
+
+        static void RunCompletionRegistryBenchmark()
+        {
+            var benchmark = new CompletionRegistryBenchmark();
+            benchmark.Init();
+            foreach (int threadCount in new int[] {1, 1, 2, 4, 8, 12})
+            {
+                foreach (bool useSharedRegistry in new bool[] {false, true})
+                {
+                    benchmark.Run(threadCount, 4 * 1000 * 1000, useSharedRegistry);
+                }
+            }
+            benchmark.Cleanup();
+        }
+
+        static void RunPInvokeByteArrayBenchmark()
+        {
+            var benchmark = new PInvokeByteArrayBenchmark();
+            benchmark.Init();
+            foreach (int threadCount in new int[] {1, 1, 2, 4, 8, 12})
+            {
+                benchmark.Run(threadCount, 4 * 1000 * 1000, 0);
+            }
+            benchmark.Cleanup();
+        }
+
+        static void RunSendMessageBenchmark()
+        {
             var benchmark = new SendMessageBenchmark();
             benchmark.Init();
             foreach (int threadCount in new int[] {1, 1, 2, 4, 8, 12})
diff --git a/src/csharp/Grpc.Microbenchmarks/SendMessageBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/SendMessageBenchmark.cs
index de67874..9cff97e 100644
--- a/src/csharp/Grpc.Microbenchmarks/SendMessageBenchmark.cs
+++ b/src/csharp/Grpc.Microbenchmarks/SendMessageBenchmark.cs
@@ -59,16 +59,16 @@
             var cq = CompletionQueueSafeHandle.CreateAsync(completionRegistry);
             var call = CreateFakeCall(cq);
 
-            var sendCompletionHandler = new SendCompletionHandler((success) => { });
+            var sendCompletionCallback = new NopSendCompletionCallback();
             var payload = new byte[payloadSize];
             var writeFlags = default(WriteFlags);
 
             var stopwatch = Stopwatch.StartNew();
             for (int i = 0; i < iterations; i++)
             {
-                call.StartSendMessage(sendCompletionHandler, payload, writeFlags, false);
+                call.StartSendMessage(sendCompletionCallback, payload, writeFlags, false);
                 var callback = completionRegistry.Extract(completionRegistry.LastRegisteredKey);
-                callback(true);
+                callback.OnComplete(true);
             }
             stopwatch.Stop();
             Console.WriteLine("Elapsed millis: " + stopwatch.ElapsedMilliseconds);
@@ -87,5 +87,13 @@
             }
             return call;
         }
+
+        private class NopSendCompletionCallback : ISendCompletionCallback
+        {
+            public void OnSendCompletion(bool success)
+            {
+                // NOP
+            }
+        }
     }
 }
diff --git a/src/csharp/Grpc.Microbenchmarks/ThreadedBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/ThreadedBenchmark.cs
index feac8d1..95b9aaa 100644
--- a/src/csharp/Grpc.Microbenchmarks/ThreadedBenchmark.cs
+++ b/src/csharp/Grpc.Microbenchmarks/ThreadedBenchmark.cs
@@ -46,6 +46,7 @@
         public void Run()
         {
             Console.WriteLine("Running threads.");
+            var gcStats = new GCStats();
             var threads = new List<Thread>();
             for (int i = 0; i < runners.Count; i++)
             {
@@ -58,7 +59,7 @@
             {
                 thread.Join();
             }
-            Console.WriteLine("All threads finished.");
+            Console.WriteLine("All threads finished (GC Stats Delta: " + gcStats.GetSnapshot() + ")");
         }
     }
 }
diff --git a/src/python/grpcio/grpc/__init__.py b/src/python/grpcio/grpc/__init__.py
index c9f9ac2..558ce42 100644
--- a/src/python/grpcio/grpc/__init__.py
+++ b/src/python/grpcio/grpc/__init__.py
@@ -424,6 +424,21 @@
         self._credentials = credentials
 
 
+class ServerCertificateConfiguration(object):
+    """A certificate configuration for use with an SSL-enabled Server.
+
+    Instances of this class can be returned in the certificate configuration
+    fetching callback.
+
+    This class has no supported interface -- it exists to define the
+    type of its instances and its instances exist to be passed to
+    other functions.
+    """
+
+    def __init__(self, certificate_configuration):
+        self._certificate_configuration = certificate_configuration
+
+
 ########################  Multi-Callable Interfaces  ###########################
 
 
@@ -1252,6 +1267,61 @@
             ], require_client_auth))
 
 
+def ssl_server_certificate_configuration(private_key_certificate_chain_pairs,
+                                         root_certificates=None):
+    """Creates a ServerCertificateConfiguration for use with a Server.
+
+    Args:
+      private_key_certificate_chain_pairs: A collection of pairs of
+        the form [PEM-encoded private key, PEM-encoded certificate
+        chain].
+      root_certificates: An optional byte string of PEM-encoded client root
+        certificates that the server will use to verify client authentication.
+
+    Returns:
+      A ServerCertificateConfiguration that can be returned in the certificate
+        configuration fetching callback.
+    """
+    if len(private_key_certificate_chain_pairs) == 0:
+        raise ValueError(
+            'At least one private key-certificate chain pair is required!')
+    else:
+        return ServerCertificateConfiguration(
+            _cygrpc.server_certificate_config_ssl(root_certificates, [
+                _cygrpc.SslPemKeyCertPair(key, pem)
+                for key, pem in private_key_certificate_chain_pairs
+            ]))
+
+
+def dynamic_ssl_server_credentials(initial_certificate_configuration,
+                                   certificate_configuration_fetcher,
+                                   require_client_authentication=False):
+    """Creates a ServerCredentials for use with an SSL-enabled Server.
+
+    Args:
+      initial_certificate_configuration (ServerCertificateConfiguration): The
+        certificate configuration with which the server will be initialized.
+      certificate_configuration_fetcher (callable): A callable that takes no
+        arguments and should return a ServerCertificateConfiguration to
+        replace the server's current certificate, or None for no change
+        (i.e., the server will continue its current certificate
+        config). The library will call this callback on *every* new
+        client connection before starting the TLS handshake with the
+        client, thus allowing the user application to optionally
+        return a new ServerCertificateConfiguration that the server will then
+        use for the handshake.
+      require_client_authentication: A boolean indicating whether or not to
+        require clients to be authenticated.
+
+    Returns:
+      A ServerCredentials.
+    """
+    return ServerCredentials(
+        _cygrpc.server_credentials_ssl_dynamic_cert_config(
+            initial_certificate_configuration,
+            certificate_configuration_fetcher, require_client_authentication))
+
+
 def channel_ready_future(channel):
     """Creates a Future that tracks when a Channel is ready.
 
@@ -1334,7 +1404,8 @@
            'ChannelConnectivity', 'StatusCode', 'RpcError', 'RpcContext',
            'Call', 'ChannelCredentials', 'CallCredentials',
            'AuthMetadataContext', 'AuthMetadataPluginCallback',
-           'AuthMetadataPlugin', 'ServerCredentials', 'UnaryUnaryMultiCallable',
+           'AuthMetadataPlugin', 'ServerCertificateConfiguration',
+           'ServerCredentials', 'UnaryUnaryMultiCallable',
            'UnaryStreamMultiCallable', 'StreamUnaryMultiCallable',
            'StreamStreamMultiCallable', 'Channel', 'ServicerContext',
            'RpcMethodHandler', 'HandlerCallDetails', 'GenericRpcHandler',
@@ -1344,8 +1415,9 @@
            'method_handlers_generic_handler', 'ssl_channel_credentials',
            'metadata_call_credentials', 'access_token_call_credentials',
            'composite_call_credentials', 'composite_channel_credentials',
-           'ssl_server_credentials', 'channel_ready_future', 'insecure_channel',
-           'secure_channel', 'server',)
+           'ssl_server_credentials', 'ssl_server_certificate_configuration',
+           'dynamic_ssl_server_credentials', 'channel_ready_future',
+           'insecure_channel', 'secure_channel', 'server',)
 
 ############################### Extension Shims ################################
 
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd.pxi
index 41975cb..bc0f185 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd.pxi
@@ -28,12 +28,27 @@
   cdef list references
 
 
+cdef class ServerCertificateConfig:
+
+  cdef grpc_ssl_server_certificate_config *c_cert_config
+  cdef const char *c_pem_root_certs
+  cdef grpc_ssl_pem_key_cert_pair *c_ssl_pem_key_cert_pairs
+  cdef size_t c_ssl_pem_key_cert_pairs_count
+  cdef list references
+
+
 cdef class ServerCredentials:
 
   cdef grpc_server_credentials *c_credentials
   cdef grpc_ssl_pem_key_cert_pair *c_ssl_pem_key_cert_pairs
   cdef size_t c_ssl_pem_key_cert_pairs_count
   cdef list references
+  # the cert config related state is used only if this credentials is
+  # created with cert config/fetcher
+  cdef object initial_cert_config
+  cdef object cert_config_fetcher
+  # whether C-core has asked for the initial_cert_config
+  cdef bint initial_cert_config_fetched
 
 
 cdef class CredentialsMetadataPlugin:
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi
index 0fabda1..ee1328d 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi
@@ -14,6 +14,7 @@
 
 cimport cpython
 
+import grpc
 import threading
 import traceback
 
@@ -58,12 +59,30 @@
     grpc_shutdown()
 
 
+cdef class ServerCertificateConfig:
+
+  def __cinit__(self):
+    grpc_init()
+    self.c_cert_config = NULL
+    self.c_pem_root_certs = NULL
+    self.c_ssl_pem_key_cert_pairs = NULL
+    self.references = []
+
+  def __dealloc__(self):
+    grpc_ssl_server_certificate_config_destroy(self.c_cert_config)
+    gpr_free(self.c_ssl_pem_key_cert_pairs)
+    grpc_shutdown()
+
+
 cdef class ServerCredentials:
 
   def __cinit__(self):
     grpc_init()
     self.c_credentials = NULL
     self.references = []
+    self.initial_cert_config = None
+    self.cert_config_fetcher = None
+    self.initial_cert_config_fetched = False
 
   def __dealloc__(self):
     if self.c_credentials != NULL:
@@ -254,34 +273,86 @@
   credentials.references.append(plugin)
   return credentials
 
-def server_credentials_ssl(pem_root_certs, pem_key_cert_pairs,
-                           bint force_client_auth):
-  pem_root_certs = str_to_bytes(pem_root_certs)
-  cdef char *c_pem_root_certs = NULL
-  if pem_root_certs is not None: 
-    c_pem_root_certs = pem_root_certs
-  pem_key_cert_pairs = list(pem_key_cert_pairs)
+cdef const char* _get_c_pem_root_certs(pem_root_certs):
+  if pem_root_certs is None:
+    return NULL
+  else:
+    return pem_root_certs
+
+cdef grpc_ssl_pem_key_cert_pair* _create_c_ssl_pem_key_cert_pairs(pem_key_cert_pairs):
+  # return a malloc'ed grpc_ssl_pem_key_cert_pair from a _list_ of SslPemKeyCertPair
   for pair in pem_key_cert_pairs:
     if not isinstance(pair, SslPemKeyCertPair):
       raise TypeError("expected pem_key_cert_pairs to be sequence of "
                       "SslPemKeyCertPair")
-  cdef ServerCredentials credentials = ServerCredentials()
-  credentials.references.append(pem_key_cert_pairs)
-  credentials.references.append(pem_root_certs)
-  credentials.c_ssl_pem_key_cert_pairs_count = len(pem_key_cert_pairs)
+  cdef size_t c_ssl_pem_key_cert_pairs_count = len(pem_key_cert_pairs)
+  cdef grpc_ssl_pem_key_cert_pair* c_ssl_pem_key_cert_pairs = NULL
   with nogil:
-    credentials.c_ssl_pem_key_cert_pairs = (
-        <grpc_ssl_pem_key_cert_pair *>gpr_malloc(
-            sizeof(grpc_ssl_pem_key_cert_pair) *
-                credentials.c_ssl_pem_key_cert_pairs_count
-        ))
-  for i in range(credentials.c_ssl_pem_key_cert_pairs_count):
-    credentials.c_ssl_pem_key_cert_pairs[i] = (
-        (<SslPemKeyCertPair>pem_key_cert_pairs[i]).c_pair)
-  credentials.c_credentials = grpc_ssl_server_credentials_create(
-      c_pem_root_certs, credentials.c_ssl_pem_key_cert_pairs,
-      credentials.c_ssl_pem_key_cert_pairs_count,
-      GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY if force_client_auth else GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
-      NULL)
+    c_ssl_pem_key_cert_pairs = (
+      <grpc_ssl_pem_key_cert_pair *>gpr_malloc(
+        sizeof(grpc_ssl_pem_key_cert_pair) * c_ssl_pem_key_cert_pairs_count))
+  for i in range(c_ssl_pem_key_cert_pairs_count):
+    c_ssl_pem_key_cert_pairs[i] = (
+      (<SslPemKeyCertPair>pem_key_cert_pairs[i]).c_pair)
+  return c_ssl_pem_key_cert_pairs
+
+def server_credentials_ssl(pem_root_certs, pem_key_cert_pairs,
+                           bint force_client_auth):
+  pem_root_certs = str_to_bytes(pem_root_certs)
+  pem_key_cert_pairs = list(pem_key_cert_pairs)
+  cdef ServerCredentials credentials = ServerCredentials()
+  credentials.references.append(pem_root_certs)
+  credentials.references.append(pem_key_cert_pairs)
+  cdef const char * c_pem_root_certs = _get_c_pem_root_certs(pem_root_certs)
+  credentials.c_ssl_pem_key_cert_pairs_count = len(pem_key_cert_pairs)
+  credentials.c_ssl_pem_key_cert_pairs = _create_c_ssl_pem_key_cert_pairs(pem_key_cert_pairs)
+  cdef grpc_ssl_server_certificate_config *c_cert_config = NULL
+  c_cert_config = grpc_ssl_server_certificate_config_create(
+    c_pem_root_certs, credentials.c_ssl_pem_key_cert_pairs,
+    credentials.c_ssl_pem_key_cert_pairs_count)
+  cdef grpc_ssl_server_credentials_options* c_options = NULL
+  # C-core assumes ownership of c_cert_config
+  c_options = grpc_ssl_server_credentials_create_options_using_config(
+    GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
+    if force_client_auth else
+    GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
+    c_cert_config)
+  # C-core assumes ownership of c_options
+  credentials.c_credentials = grpc_ssl_server_credentials_create_with_options(c_options)
   return credentials
 
+def server_certificate_config_ssl(pem_root_certs, pem_key_cert_pairs):
+  pem_root_certs = str_to_bytes(pem_root_certs)
+  pem_key_cert_pairs = list(pem_key_cert_pairs)
+  cdef ServerCertificateConfig cert_config = ServerCertificateConfig()
+  cert_config.references.append(pem_root_certs)
+  cert_config.references.append(pem_key_cert_pairs)
+  cert_config.c_pem_root_certs = _get_c_pem_root_certs(pem_root_certs)
+  cert_config.c_ssl_pem_key_cert_pairs_count = len(pem_key_cert_pairs)
+  cert_config.c_ssl_pem_key_cert_pairs = _create_c_ssl_pem_key_cert_pairs(pem_key_cert_pairs)
+  cert_config.c_cert_config = grpc_ssl_server_certificate_config_create(
+    cert_config.c_pem_root_certs, cert_config.c_ssl_pem_key_cert_pairs,
+    cert_config.c_ssl_pem_key_cert_pairs_count)
+  return cert_config
+
+def server_credentials_ssl_dynamic_cert_config(initial_cert_config,
+                                               cert_config_fetcher,
+                                               bint force_client_auth):
+  if not isinstance(initial_cert_config, grpc.ServerCertificateConfiguration):
+    raise TypeError(
+        'initial_cert_config must be a grpc.ServerCertificateConfiguration')
+  if not callable(cert_config_fetcher):
+    raise TypeError('cert_config_fetcher must be callable')
+  cdef ServerCredentials credentials = ServerCredentials()
+  credentials.initial_cert_config = initial_cert_config
+  credentials.cert_config_fetcher = cert_config_fetcher
+  cdef grpc_ssl_server_credentials_options* c_options = NULL
+  c_options = grpc_ssl_server_credentials_create_options_using_config_fetcher(
+    GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
+    if force_client_auth else
+    GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
+    _server_cert_config_fetcher_wrapper,
+    <void*>credentials)
+  # C-core assumes ownership of c_options
+  credentials.c_credentials = grpc_ssl_server_credentials_create_with_options(c_options)
+  return credentials
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
index f115106..660263f 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
@@ -391,6 +391,42 @@
     GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY
     GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
 
+  ctypedef enum grpc_ssl_certificate_config_reload_status:
+    GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_UNCHANGED
+    GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_NEW
+    GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_FAIL
+
+  ctypedef struct grpc_ssl_server_certificate_config:
+    # We don't care about the internals
+    pass
+
+  ctypedef struct grpc_ssl_server_credentials_options:
+    # We don't care about the internals
+    pass
+
+  grpc_ssl_server_certificate_config * grpc_ssl_server_certificate_config_create(
+    const char *pem_root_certs,
+    const grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs,
+    size_t num_key_cert_pairs)
+
+  void grpc_ssl_server_certificate_config_destroy(grpc_ssl_server_certificate_config *config)
+
+  ctypedef grpc_ssl_certificate_config_reload_status (*grpc_ssl_server_certificate_config_callback)(
+    void *user_data,
+    grpc_ssl_server_certificate_config **config)
+
+  grpc_ssl_server_credentials_options *grpc_ssl_server_credentials_create_options_using_config(
+    grpc_ssl_client_certificate_request_type client_certificate_request,
+    grpc_ssl_server_certificate_config *certificate_config)
+
+  grpc_ssl_server_credentials_options* grpc_ssl_server_credentials_create_options_using_config_fetcher(
+    grpc_ssl_client_certificate_request_type client_certificate_request,
+    grpc_ssl_server_certificate_config_callback cb,
+    void *user_data)
+
+  grpc_server_credentials *grpc_ssl_server_credentials_create_with_options(
+      grpc_ssl_server_credentials_options *options)
+
   ctypedef struct grpc_ssl_pem_key_cert_pair:
     const char *private_key
     const char *certificate_chain "cert_chain"
@@ -440,10 +476,6 @@
     # We don't care about the internals (and in fact don't know them)
     pass
 
-  grpc_server_credentials *grpc_ssl_server_credentials_create(
-      const char *pem_root_certs,
-      grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs,
-      size_t num_key_cert_pairs, int force_client_auth, void *reserved)
   void grpc_server_credentials_release(grpc_server_credentials *creds) nogil
 
   int grpc_server_add_secure_http2_port(grpc_server *server, const char *addr,
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi
index b8db274..5f34059 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi
@@ -14,8 +14,46 @@
 
 cimport cpython
 
+import logging
 import time
+import grpc
 
+cdef grpc_ssl_certificate_config_reload_status _server_cert_config_fetcher_wrapper(
+        void* user_data, grpc_ssl_server_certificate_config **config) with gil:
+  # This is a credentials.ServerCertificateConfig
+  cdef ServerCertificateConfig cert_config = None
+  if not user_data:
+    raise ValueError('internal error: user_data must be specified')
+  credentials = <ServerCredentials>user_data
+  if not credentials.initial_cert_config_fetched:
+    # C-core is asking for the initial cert config
+    credentials.initial_cert_config_fetched = True
+    cert_config = credentials.initial_cert_config._certificate_configuration
+  else:
+    user_cb = credentials.cert_config_fetcher
+    try:
+      cert_config_wrapper = user_cb()
+    except Exception:
+      logging.exception('Error fetching certificate config')
+      return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_FAIL
+    if cert_config_wrapper is None:
+      return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_UNCHANGED
+    elif not isinstance(
+        cert_config_wrapper, grpc.ServerCertificateConfiguration):
+      logging.error(
+          'Error fetching certificate configuration: certificate '
+          'configuration must be of type grpc.ServerCertificateConfiguration, '
+          'not %s' % type(cert_config_wrapper).__name__)
+      return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_FAIL
+    else:
+      cert_config = cert_config_wrapper._certificate_configuration
+  config[0] = <grpc_ssl_server_certificate_config*>cert_config.c_cert_config
+  # our caller will assume ownership of memory, so we have to recreate
+  # a copy of c_cert_config here
+  cert_config.c_cert_config = grpc_ssl_server_certificate_config_create(
+      cert_config.c_pem_root_certs, cert_config.c_ssl_pem_key_cert_pairs,
+      cert_config.c_ssl_pem_key_cert_pairs_count)
+  return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_NEW
 
 cdef class Server:
 
diff --git a/src/python/grpcio_health_checking/setup.py b/src/python/grpcio_health_checking/setup.py
index 01d796f..7a2e6f5 100644
--- a/src/python/grpcio_health_checking/setup.py
+++ b/src/python/grpcio_health_checking/setup.py
@@ -60,17 +60,8 @@
                     'grpcio>={version}'.format(version=grpc_version.VERSION),)
 
 try:
-    # ensure we can load the _pb2_grpc module:
-    from grpc_health.v1 import health_pb2_grpc as _pb2_grpc
-    # if we can find the _pb2_grpc module, the package has already been built.
-    SETUP_REQUIRES = ()
-    COMMAND_CLASS = {
-        # wire up commands to no-op not to break the external dependencies
-        'preprocess': _NoOpCommand,
-        'build_package_protos': _NoOpCommand,
-    }
-except ImportError:  # we are in the build environment
     import health_commands as _health_commands
+    # we are in the build environment, otherwise the above import fails
     SETUP_REQUIRES = (
         'grpcio-tools=={version}'.format(version=grpc_version.VERSION),)
     COMMAND_CLASS = {
@@ -78,6 +69,13 @@
         'preprocess': _health_commands.CopyProtoModules,
         'build_package_protos': _health_commands.BuildPackageProtos,
     }
+except ImportError:
+    SETUP_REQUIRES = ()
+    COMMAND_CLASS = {
+        # wire up commands to no-op not to break the external dependencies
+        'preprocess': _NoOpCommand,
+        'build_package_protos': _NoOpCommand,
+    }
 
 setuptools.setup(
     name='grpcio-health-checking',
diff --git a/src/python/grpcio_reflection/setup.py b/src/python/grpcio_reflection/setup.py
index ad9e869..25312c7 100644
--- a/src/python/grpcio_reflection/setup.py
+++ b/src/python/grpcio_reflection/setup.py
@@ -61,17 +61,8 @@
                     'grpcio>={version}'.format(version=grpc_version.VERSION),)
 
 try:
-    # ensure we can load the _pb2_grpc module:
-    from grpc_reflection.v1alpha import reflection_pb2_grpc as _pb2_grpc
-    # if we can find the _pb2_grpc module, the package has already been built.
-    SETUP_REQUIRES = ()
-    COMMAND_CLASS = {
-        # wire up commands to no-op not to break the external dependencies
-        'preprocess': _NoOpCommand,
-        'build_package_protos': _NoOpCommand,
-    }
-except ImportError:  # we are in the build environment
     import reflection_commands as _reflection_commands
+    # we are in the build environment, otherwise the above import fails
     SETUP_REQUIRES = (
         'grpcio-tools=={version}'.format(version=grpc_version.VERSION),)
     COMMAND_CLASS = {
@@ -79,6 +70,13 @@
         'preprocess': _reflection_commands.CopyProtoModules,
         'build_package_protos': _reflection_commands.BuildPackageProtos,
     }
+except ImportError:
+    SETUP_REQUIRES = ()
+    COMMAND_CLASS = {
+        # wire up commands to no-op not to break the external dependencies
+        'preprocess': _NoOpCommand,
+        'build_package_protos': _NoOpCommand,
+    }
 
 setuptools.setup(
     name='grpcio-reflection',
diff --git a/src/python/grpcio_tests/tests/interop/client.py b/src/python/grpcio_tests/tests/interop/client.py
index e520c08..383b5f0 100644
--- a/src/python/grpcio_tests/tests/interop/client.py
+++ b/src/python/grpcio_tests/tests/interop/client.py
@@ -29,37 +29,40 @@
     parser = argparse.ArgumentParser()
     parser.add_argument(
         '--server_host',
-        help='the host to which to connect',
+        default="localhost",
         type=str,
-        default="localhost")
+        help='the host to which to connect')
     parser.add_argument(
-        '--server_port', help='the port to which to connect', type=int)
+        '--server_port',
+        type=int,
+        required=True,
+        help='the port to which to connect')
     parser.add_argument(
         '--test_case',
-        help='the test case to execute',
+        default='large_unary',
         type=str,
-        default="large_unary")
+        help='the test case to execute')
     parser.add_argument(
         '--use_tls',
-        help='require a secure connection',
         default=False,
-        type=resources.parse_bool)
+        type=resources.parse_bool,
+        help='require a secure connection')
     parser.add_argument(
         '--use_test_ca',
-        help='replace platform root CAs with ca.pem',
         default=False,
-        type=resources.parse_bool)
+        type=resources.parse_bool,
+        help='replace platform root CAs with ca.pem')
     parser.add_argument(
         '--server_host_override',
         default="foo.test.google.fr",
-        help='the server host to which to claim to connect',
-        type=str)
+        type=str,
+        help='the server host to which to claim to connect')
     parser.add_argument(
-        '--oauth_scope', help='scope for OAuth tokens', type=str)
+        '--oauth_scope', type=str, help='scope for OAuth tokens')
     parser.add_argument(
         '--default_service_account',
-        help='email address of the default service account',
-        type=str)
+        type=str,
+        help='email address of the default service account')
     return parser.parse_args()
 
 
diff --git a/src/python/grpcio_tests/tests/interop/server.py b/src/python/grpcio_tests/tests/interop/server.py
index 8ad1f5f..eeb41a2 100644
--- a/src/python/grpcio_tests/tests/interop/server.py
+++ b/src/python/grpcio_tests/tests/interop/server.py
@@ -29,12 +29,13 @@
 
 def serve():
     parser = argparse.ArgumentParser()
-    parser.add_argument('--port', help='the port on which to serve', type=int)
+    parser.add_argument(
+        '--port', type=int, required=True, help='the port on which to serve')
     parser.add_argument(
         '--use_tls',
-        help='require a secure connection',
         default=False,
-        type=resources.parse_bool)
+        type=resources.parse_bool,
+        help='require a secure connection')
     args = parser.parse_args()
 
     server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
diff --git a/src/python/grpcio_tests/tests/tests.json b/src/python/grpcio_tests/tests/tests.json
index 8512d5b..e277a3e 100644
--- a/src/python/grpcio_tests/tests/tests.json
+++ b/src/python/grpcio_tests/tests/tests.json
@@ -46,6 +46,10 @@
   "unit._reconnect_test.ReconnectTest",
   "unit._resource_exhausted_test.ResourceExhaustedTest",
   "unit._rpc_test.RPCTest",
+  "unit._server_ssl_cert_config_test.ServerSSLCertConfigFetcherParamsChecks",
+  "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestCertConfigReuse",
+  "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestWithClientAuth",
+  "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestWithoutClientAuth",
   "unit._thread_cleanup_test.CleanupThreadTest",
   "unit.beta._beta_features_test.BetaFeaturesTest",
   "unit.beta._beta_features_test.ContextManagementAndLifecycleTest",
diff --git a/src/python/grpcio_tests/tests/unit/_api_test.py b/src/python/grpcio_tests/tests/unit/_api_test.py
index a3351aa..b14e8d5 100644
--- a/src/python/grpcio_tests/tests/unit/_api_test.py
+++ b/src/python/grpcio_tests/tests/unit/_api_test.py
@@ -30,18 +30,20 @@
             'ChannelConnectivity', 'StatusCode', 'RpcError', 'RpcContext',
             'Call', 'ChannelCredentials', 'CallCredentials',
             'AuthMetadataContext', 'AuthMetadataPluginCallback',
-            'AuthMetadataPlugin', 'ServerCredentials',
-            'UnaryUnaryMultiCallable', 'UnaryStreamMultiCallable',
-            'StreamUnaryMultiCallable', 'StreamStreamMultiCallable', 'Channel',
-            'ServicerContext', 'RpcMethodHandler', 'HandlerCallDetails',
-            'GenericRpcHandler', 'ServiceRpcHandler', 'Server',
-            'unary_unary_rpc_method_handler', 'unary_stream_rpc_method_handler',
+            'AuthMetadataPlugin', 'ServerCertificateConfiguration',
+            'ServerCredentials', 'UnaryUnaryMultiCallable',
+            'UnaryStreamMultiCallable', 'StreamUnaryMultiCallable',
+            'StreamStreamMultiCallable', 'Channel', 'ServicerContext',
+            'RpcMethodHandler', 'HandlerCallDetails', 'GenericRpcHandler',
+            'ServiceRpcHandler', 'Server', 'unary_unary_rpc_method_handler',
+            'unary_stream_rpc_method_handler',
             'stream_unary_rpc_method_handler',
             'stream_stream_rpc_method_handler',
             'method_handlers_generic_handler', 'ssl_channel_credentials',
             'metadata_call_credentials', 'access_token_call_credentials',
             'composite_call_credentials', 'composite_channel_credentials',
-            'ssl_server_credentials', 'channel_ready_future',
+            'ssl_server_credentials', 'ssl_server_certificate_configuration',
+            'dynamic_ssl_server_credentials', 'channel_ready_future',
             'insecure_channel', 'secure_channel', 'server',)
 
         six.assertCountEqual(self, expected_grpc_code_elements,
diff --git a/src/python/grpcio_tests/tests/unit/_server_ssl_cert_config_test.py b/src/python/grpcio_tests/tests/unit/_server_ssl_cert_config_test.py
new file mode 100644
index 0000000..005d16e
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/_server_ssl_cert_config_test.py
@@ -0,0 +1,519 @@
+# 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.
+"""Tests server certificate rotation.
+
+Here we test various aspects of gRPC Python, and in some cases gRPC
+Core by extension, support for server certificate rotation.
+
+* ServerSSLCertReloadTestWithClientAuth: test ability to rotate
+  server's SSL cert for use in future channels with clients while not
+  affecting any existing channel. The server requires client
+  authentication.
+
+* ServerSSLCertReloadTestWithoutClientAuth: like
+  ServerSSLCertReloadTestWithClientAuth except that the server does
+  not authenticate the client.
+
+* ServerSSLCertReloadTestCertConfigReuse: tests gRPC Python's ability
+  to deal with user's reuse of ServerCertificateConfiguration instances.
+"""
+
+import abc
+import collections
+import os
+import six
+import threading
+import unittest
+
+from concurrent import futures
+
+import grpc
+from tests.unit import resources
+from tests.testing import _application_common
+from tests.testing import _server_application
+from tests.testing.proto import services_pb2_grpc
+
+CA_1_PEM = resources.cert_hier_1_root_ca_cert()
+CA_2_PEM = resources.cert_hier_2_root_ca_cert()
+
+CLIENT_KEY_1_PEM = resources.cert_hier_1_client_1_key()
+CLIENT_CERT_CHAIN_1_PEM = (resources.cert_hier_1_client_1_cert() +
+                           resources.cert_hier_1_intermediate_ca_cert())
+
+CLIENT_KEY_2_PEM = resources.cert_hier_2_client_1_key()
+CLIENT_CERT_CHAIN_2_PEM = (resources.cert_hier_2_client_1_cert() +
+                           resources.cert_hier_2_intermediate_ca_cert())
+
+SERVER_KEY_1_PEM = resources.cert_hier_1_server_1_key()
+SERVER_CERT_CHAIN_1_PEM = (resources.cert_hier_1_server_1_cert() +
+                           resources.cert_hier_1_intermediate_ca_cert())
+
+SERVER_KEY_2_PEM = resources.cert_hier_2_server_1_key()
+SERVER_CERT_CHAIN_2_PEM = (resources.cert_hier_2_server_1_cert() +
+                           resources.cert_hier_2_intermediate_ca_cert())
+
+# for use with the CertConfigFetcher. Roughly a simple custom mock
+# implementation
+Call = collections.namedtuple('Call', ['did_raise', 'returned_cert_config'])
+
+
+def _create_client_stub(
+        port,
+        expect_success,
+        root_certificates=None,
+        private_key=None,
+        certificate_chain=None,):
+    channel = grpc.secure_channel('localhost:{}'.format(port),
+                                  grpc.ssl_channel_credentials(
+                                      root_certificates=root_certificates,
+                                      private_key=private_key,
+                                      certificate_chain=certificate_chain))
+    if expect_success:
+        # per Nathaniel: there's some robustness issue if we start
+        # using a channel without waiting for it to be actually ready
+        grpc.channel_ready_future(channel).result(timeout=10)
+    return services_pb2_grpc.FirstServiceStub(channel)
+
+
+class CertConfigFetcher(object):
+
+    def __init__(self):
+        self._lock = threading.Lock()
+        self._calls = []
+        self._should_raise = False
+        self._cert_config = None
+
+    def reset(self):
+        with self._lock:
+            self._calls = []
+            self._should_raise = False
+            self._cert_config = None
+
+    def configure(self, should_raise, cert_config):
+        assert not (should_raise and cert_config), (
+            "should not specify both should_raise and a cert_config at the same time"
+        )
+        with self._lock:
+            self._should_raise = should_raise
+            self._cert_config = cert_config
+
+    def getCalls(self):
+        with self._lock:
+            return self._calls
+
+    def __call__(self):
+        with self._lock:
+            if self._should_raise:
+                self._calls.append(Call(True, None))
+                raise ValueError('just for fun, should not affect the test')
+            else:
+                self._calls.append(Call(False, self._cert_config))
+                return self._cert_config
+
+
+class _ServerSSLCertReloadTest(
+        six.with_metaclass(abc.ABCMeta, unittest.TestCase)):
+
+    def __init__(self, *args, **kwargs):
+        super(_ServerSSLCertReloadTest, self).__init__(*args, **kwargs)
+        self.server = None
+        self.port = None
+
+    @abc.abstractmethod
+    def require_client_auth(self):
+        raise NotImplementedError()
+
+    def setUp(self):
+        self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+        services_pb2_grpc.add_FirstServiceServicer_to_server(
+            _server_application.FirstServiceServicer(), self.server)
+        switch_cert_on_client_num = 10
+        initial_cert_config = grpc.ssl_server_certificate_configuration(
+            [(SERVER_KEY_1_PEM, SERVER_CERT_CHAIN_1_PEM)],
+            root_certificates=CA_2_PEM)
+        self.cert_config_fetcher = CertConfigFetcher()
+        server_credentials = grpc.dynamic_ssl_server_credentials(
+            initial_cert_config,
+            self.cert_config_fetcher,
+            require_client_authentication=self.require_client_auth())
+        self.port = self.server.add_secure_port('[::]:0', server_credentials)
+        self.server.start()
+
+    def tearDown(self):
+        if self.server:
+            self.server.stop(None)
+
+    def _perform_rpc(self, client_stub, expect_success):
+        # we don't care about the actual response of the rpc; only
+        # whether we can perform it or not, and if not, the status
+        # code must be UNAVAILABLE
+        request = _application_common.UNARY_UNARY_REQUEST
+        if expect_success:
+            response = client_stub.UnUn(request)
+            self.assertEqual(response, _application_common.UNARY_UNARY_RESPONSE)
+        else:
+            with self.assertRaises(grpc.RpcError) as exception_context:
+                client_stub.UnUn(request)
+            self.assertEqual(exception_context.exception.code(),
+                             grpc.StatusCode.UNAVAILABLE)
+
+    def _do_one_shot_client_rpc(self,
+                                expect_success,
+                                root_certificates=None,
+                                private_key=None,
+                                certificate_chain=None):
+        client_stub = _create_client_stub(
+            self.port,
+            expect_success,
+            root_certificates=root_certificates,
+            private_key=private_key,
+            certificate_chain=certificate_chain)
+        self._perform_rpc(client_stub, expect_success)
+        del client_stub
+
+    def _test(self):
+        # things should work...
+        self.cert_config_fetcher.configure(False, None)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertIsNone(actual_calls[0].returned_cert_config)
+
+        # client should reject server...
+        # fails because client trusts ca2 and so will reject server
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._do_one_shot_client_rpc(
+            False,
+            root_certificates=CA_2_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertGreaterEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        for i, call in enumerate(actual_calls):
+            self.assertFalse(call.did_raise, 'i= {}'.format(i))
+            self.assertIsNone(call.returned_cert_config, 'i= {}'.format(i))
+
+        # should work again...
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(True, None)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertTrue(actual_calls[0].did_raise)
+        self.assertIsNone(actual_calls[0].returned_cert_config)
+
+        # if with_client_auth, then client should be rejected by
+        # server because client uses key/cert1, but server trusts ca2,
+        # so server will reject
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._do_one_shot_client_rpc(
+            not self.require_client_auth(),
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_1_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_1_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertGreaterEqual(len(actual_calls), 1)
+        for i, call in enumerate(actual_calls):
+            self.assertFalse(call.did_raise, 'i= {}'.format(i))
+            self.assertIsNone(call.returned_cert_config, 'i= {}'.format(i))
+
+        # should work again...
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertIsNone(actual_calls[0].returned_cert_config)
+
+        # now create the "persistent" clients
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        persistent_client_stub_A = _create_client_stub(
+            self.port,
+            True,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        self._perform_rpc(persistent_client_stub_A, True)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertIsNone(actual_calls[0].returned_cert_config)
+
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        persistent_client_stub_B = _create_client_stub(
+            self.port,
+            True,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        self._perform_rpc(persistent_client_stub_B, True)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertIsNone(actual_calls[0].returned_cert_config)
+
+        # moment of truth!! client should reject server because the
+        # server switch cert...
+        cert_config = grpc.ssl_server_certificate_configuration(
+            [(SERVER_KEY_2_PEM, SERVER_CERT_CHAIN_2_PEM)],
+            root_certificates=CA_1_PEM)
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, cert_config)
+        self._do_one_shot_client_rpc(
+            False,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertGreaterEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        for i, call in enumerate(actual_calls):
+            self.assertFalse(call.did_raise, 'i= {}'.format(i))
+            self.assertEqual(call.returned_cert_config, cert_config,
+                             'i= {}'.format(i))
+
+        # now should work again...
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_2_PEM,
+            private_key=CLIENT_KEY_1_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_1_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertIsNone(actual_calls[0].returned_cert_config)
+
+        # client should be rejected by server if with_client_auth
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._do_one_shot_client_rpc(
+            not self.require_client_auth(),
+            root_certificates=CA_2_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertGreaterEqual(len(actual_calls), 1)
+        for i, call in enumerate(actual_calls):
+            self.assertFalse(call.did_raise, 'i= {}'.format(i))
+            self.assertIsNone(call.returned_cert_config, 'i= {}'.format(i))
+
+        # here client should reject server...
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._do_one_shot_client_rpc(
+            False,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertGreaterEqual(len(actual_calls), 1)
+        for i, call in enumerate(actual_calls):
+            self.assertFalse(call.did_raise, 'i= {}'.format(i))
+            self.assertIsNone(call.returned_cert_config, 'i= {}'.format(i))
+
+        # persistent clients should continue to work
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._perform_rpc(persistent_client_stub_A, True)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 0)
+
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, None)
+        self._perform_rpc(persistent_client_stub_B, True)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 0)
+
+
+class ServerSSLCertConfigFetcherParamsChecks(unittest.TestCase):
+
+    def test_check_on_initial_config(self):
+        with self.assertRaises(TypeError):
+            grpc.dynamic_ssl_server_credentials(None, str)
+        with self.assertRaises(TypeError):
+            grpc.dynamic_ssl_server_credentials(1, str)
+
+    def test_check_on_config_fetcher(self):
+        cert_config = grpc.ssl_server_certificate_configuration(
+            [(SERVER_KEY_2_PEM, SERVER_CERT_CHAIN_2_PEM)],
+            root_certificates=CA_1_PEM)
+        with self.assertRaises(TypeError):
+            grpc.dynamic_ssl_server_credentials(cert_config, None)
+        with self.assertRaises(TypeError):
+            grpc.dynamic_ssl_server_credentials(cert_config, 1)
+
+
+class ServerSSLCertReloadTestWithClientAuth(_ServerSSLCertReloadTest):
+
+    def require_client_auth(self):
+        return True
+
+    test = _ServerSSLCertReloadTest._test
+
+
+class ServerSSLCertReloadTestWithoutClientAuth(_ServerSSLCertReloadTest):
+
+    def require_client_auth(self):
+        return False
+
+    test = _ServerSSLCertReloadTest._test
+
+
+class ServerSSLCertReloadTestCertConfigReuse(_ServerSSLCertReloadTest):
+    """Ensures that `ServerCertificateConfiguration` instances can be reused.
+
+    Because gRPC Core takes ownership of the
+    `grpc_ssl_server_certificate_config` encapsulated by
+    `ServerCertificateConfiguration`, this test reuses the same
+    `ServerCertificateConfiguration` instances multiple times to make sure
+    gRPC Python takes care of maintaining the validity of
+    `ServerCertificateConfiguration` instances, so that such instances can be
+    re-used by user application.
+    """
+
+    def require_client_auth(self):
+        return True
+
+    def setUp(self):
+        self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+        services_pb2_grpc.add_FirstServiceServicer_to_server(
+            _server_application.FirstServiceServicer(), self.server)
+        self.cert_config_A = grpc.ssl_server_certificate_configuration(
+            [(SERVER_KEY_1_PEM, SERVER_CERT_CHAIN_1_PEM)],
+            root_certificates=CA_2_PEM)
+        self.cert_config_B = grpc.ssl_server_certificate_configuration(
+            [(SERVER_KEY_2_PEM, SERVER_CERT_CHAIN_2_PEM)],
+            root_certificates=CA_1_PEM)
+        self.cert_config_fetcher = CertConfigFetcher()
+        server_credentials = grpc.dynamic_ssl_server_credentials(
+            self.cert_config_A,
+            self.cert_config_fetcher,
+            require_client_authentication=True)
+        self.port = self.server.add_secure_port('[::]:0', server_credentials)
+        self.server.start()
+
+    def test_cert_config_reuse(self):
+
+        # succeed with A
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, self.cert_config_A)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertEqual(actual_calls[0].returned_cert_config,
+                         self.cert_config_A)
+
+        # fail with A
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, self.cert_config_A)
+        self._do_one_shot_client_rpc(
+            False,
+            root_certificates=CA_2_PEM,
+            private_key=CLIENT_KEY_1_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_1_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertGreaterEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        for i, call in enumerate(actual_calls):
+            self.assertFalse(call.did_raise, 'i= {}'.format(i))
+            self.assertEqual(call.returned_cert_config, self.cert_config_A,
+                             'i= {}'.format(i))
+
+        # succeed again with A
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, self.cert_config_A)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertEqual(actual_calls[0].returned_cert_config,
+                         self.cert_config_A)
+
+        # succeed with B
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, self.cert_config_B)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_2_PEM,
+            private_key=CLIENT_KEY_1_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_1_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertEqual(actual_calls[0].returned_cert_config,
+                         self.cert_config_B)
+
+        # fail with B
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, self.cert_config_B)
+        self._do_one_shot_client_rpc(
+            False,
+            root_certificates=CA_1_PEM,
+            private_key=CLIENT_KEY_2_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_2_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertGreaterEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        for i, call in enumerate(actual_calls):
+            self.assertFalse(call.did_raise, 'i= {}'.format(i))
+            self.assertEqual(call.returned_cert_config, self.cert_config_B,
+                             'i= {}'.format(i))
+
+        # succeed again with B
+        self.cert_config_fetcher.reset()
+        self.cert_config_fetcher.configure(False, self.cert_config_B)
+        self._do_one_shot_client_rpc(
+            True,
+            root_certificates=CA_2_PEM,
+            private_key=CLIENT_KEY_1_PEM,
+            certificate_chain=CLIENT_CERT_CHAIN_1_PEM)
+        actual_calls = self.cert_config_fetcher.getCalls()
+        self.assertEqual(len(actual_calls), 1)
+        self.assertFalse(actual_calls[0].did_raise)
+        self.assertEqual(actual_calls[0].returned_cert_config,
+                         self.cert_config_B)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/src/python/grpcio_tests/tests/unit/credentials/README b/src/python/grpcio_tests/tests/unit/credentials/README
deleted file mode 100644
index cb20dcb..0000000
--- a/src/python/grpcio_tests/tests/unit/credentials/README
+++ /dev/null
@@ -1 +0,0 @@
-These are test keys *NOT* to be used in production.
diff --git a/src/python/grpcio_tests/tests/unit/credentials/README.md b/src/python/grpcio_tests/tests/unit/credentials/README.md
new file mode 100644
index 0000000..100b43c
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/README.md
@@ -0,0 +1,15 @@
+These are test keys *NOT* to be used in production.
+
+The `certificate_hierarchy_1` and `certificate_hierarchy_2` contain
+two disjoint but similarly organized certificate hierarchies. Each
+contains:
+
+* The respective root CA cert in `certs/ca.cert.pem`
+
+* The intermediate CA cert in
+  `intermediate/certs/intermediate.cert.pem`, signed by the root CA
+
+* A client cert and a server cert--both signed by the intermediate
+  CA--in `intermediate/certs/client.cert.pem` and
+  `intermediate/certs/localhost-1.cert.pem`; the corresponding keys
+  are in `intermediate/private`
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/certs/ca.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/certs/ca.cert.pem
new file mode 100644
index 0000000..604b86f
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/certs/ca.cert.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFZDCCA0ygAwIBAgIJAKfkDFZ6+Ly/MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV
+BAYTAnVzMQ4wDAYDVQQIDAVkdW1teTEOMAwGA1UECgwFZHVtbXkxEDAOBgNVBAMM
+B3Jvb3QgY2EwHhcNMTcxMTAyMDAzNzA1WhcNMzcxMDI4MDAzNzA1WjA/MQswCQYD
+VQQGEwJ1czEOMAwGA1UECAwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MRAwDgYDVQQD
+DAdyb290IGNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxlSUuSbi
+o66tT2ZCqu9wNqSX8VhAJkmrAT5y6m2V0VlQ8Gz7ddynW5UVSmtvDNTebZ15FrvO
+6Ng7QnwXXNs/dEzl6oMe6AKDZpuWScVkiqH1UYWBkMLRygWCTEYpSTWTpZWk1zxj
+DJ2LlIoO1X/ufLyLOfy2a2XEz8ICzJePmqVca6fmfEtCTj1/8FcwCBF6YlUWVzlR
+wewjanQo/lorTYbub+Q6LGxPXZ8W0qoKZzLDSD9cnj4pcJzGGFeu9KkNaW4rldZG
+t7mTGQqIRc98dDRc9Jb7PqL8tMPLidw1KErUi05ofxggc5vqNnj4xBl6aX6b/EYN
+rBLzO2e0FazX6TwNKwwg68vbOanpDq5LVmIUH8bY1zNZ+JPBGO9pXlAA0YwLx86r
+R7YhQ431ZpJ2KGnYjVhYnZ2L3NjV3UYX3x5Z3OrDj9hybhucJB48DMQ1+loEabwK
+fSUJtcSPc8dCIibxVKidBFgaTPXtHy2MPXuhMhR7PCtMpE7RPUoYmdZLr9FNN1ty
+/RAbwBfuhGLbRI2qqJgbOzHJHaOY/FtShfooLz7lt4LIjPTARaNsulG2rbv+m3z9
+mhNjL+peV8gni/xyOYYTbdzZagLrtSHeTWsITvmVt0flMHkjHyv35rw23+hBlSjp
+6+S+0MmwuwxqBBccBSlZ9t3Xh1N+vFkb2UkCAwEAAaNjMGEwHQYDVR0OBBYEFJWE
+tQwTbTCgZWNN08VSxjdNA0oaMB8GA1UdIwQYMBaAFJWEtQwTbTCgZWNN08VSxjdN
+A0oaMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+CwUAA4ICAQCNjPv/e3ozX1PuN5Tluf0yOmKCxKVCK3Pp98WkDzH4Rp1urEeYrGJL
+vBNcl17avOJ0e+zTVYPXFviFbsBsU/zaf+TqEujXabsdL+nvvCJ2mMqYn4wyDFjS
+zDNbGH6O0ENZz5NSY0/UGSOHYrYnYB94QRFLbbf0Y3PmBS2eRNjIUnv7ytPZNMi/
+piM+QhPb0Ebyk0rHQZ0RAJaC/wsEtqP8TGV/fx+AzG7zW/zxgPTrgIThy138tLQ+
+xCVDP9H2c17nVP6vjYzKnMZ94uGrGqUzV9vU7EqYl0uZflIf98pLfdKHnQ3heqds
+8KQPNKRxVvcc92qv2pQY951wb1fkhLutjHn7TUvrenyAngz+Vs19NxbqLPys1CTw
+iaL7vZ8VE/aEDm1tjt5SLM474tpATjk1+qMRaWnii8J5rTodYHP+Zu2GxyIrMiGq
+tfNZMYI0tETK1XmEo75E/3s9pmIeQNGKLFp+qL7xrVyN/2ffNv0at8kkqXluunK9
+/Ki0gKYlGFm4Eu8t/nHMqhBx/njYg6pLDuarLW6ftUV7aHd7qKcCWOWqK6gnH/vX
+3Apv31eltZBBVN69p3CFy2oMnjrom2Yn/DUXFwrJLBiNJ1dd1JyDxpqpJ74ZQy+/
+pSRWMTRM5SuC7lYARx5rYPmp6cZJWyWRH/3r7bwS699/W965pa5nug==
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/client.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/client.cert.pem
new file mode 100644
index 0000000..44bc562
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/client.cert.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIExzCCAq+gAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCdXMx
+DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEYMBYGA1UEAwwPaW50ZXJt
+ZWRpYXRlIGNhMB4XDTE3MTEwMjAwMzcwNloXDTI3MTAzMTAwMzcwNlowPjELMAkG
+A1UEBhMCdXMxDjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEPMA0GA1UE
+AwwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwquL6gtP
+R7P9xJK76FTj8fI5TSJa3cAMt1p6CmessjHQq7nQ6DWLGVi4XIt9Sc/1C3rXupOe
+90Ok4L0tsuVZH78Wn0EBmBH7S4IbhU9P+aJ9mcigepj1lnxWqoVblgeJYKMOOwAf
+pAKUNMWDSm+nCfwE+R5d8d8cfA41Awq1jTRjOVpiJq6aoKfs791a1ZkZde3kFrNV
+AVjC06GgA1lZd3sHf94hmLeC+xJztRXVE9e+7dcc7nFDH0t5DIKYBAklsHg77mZa
+3IK4aOZew7Lm6diPoMnAzXh2rWpJU6RrEE29gIkJBsF8CL1Ndg9MzssCg6KBjoai
+Vt5dJ+4TSEGCOwIDAQABo4HFMIHCMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD
+AgWgMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBDbGllbnQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFPeuKDCswk8jaH9tl6X+uXjo+WM1MB8GA1UdIwQY
+MBaAFCoqYgmKh3CUafVp+paXxfz+He+FMA4GA1UdDwEB/wQEAwIF4DAdBgNVHSUE
+FjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwDQYJKoZIhvcNAQELBQADggIBADYAp8XS
+UjMEpX/zVjRWpAAT4HNEJylCV1QNyhBIAyx38A6xJYuFIx966Htd6W9/Rw4sUY6y
+F4pOmnLCRxIPqFzzMYcBHSpynlcu5G7zqIod3wYIk7BNlB0AzkZn6yD8bM1y5Plf
+myzQVDEGggrDtaW2EehhNIB+wOmbRGITjIcZUEr8V4BlLXkCqOuxauxl82d5/k2w
+LAhjOb9d1VW6RT8+Lcn6drhHZdvtSCe8Z27BcXhaQLL8366mhbigKYJt5adD0KOx
+pl0MQcoL1Rth5cJEj+1/lgUaxcnvh7TaIIGEx0h3olQXsTxSTypU/nww2Ic41xdG
+xl3xvHsxe20IvOOAMRfS/LPW7MCtQ3k0BqB/rAQvmB0r5YITLlMJuBqg+zjYrG/j
+s5szSGAz9r0leFuPraeuZA41d9UBTAJMoVrrQZ4xVHMXQi1oz9E9KlIdbO9+spvC
+ulfO+D+Z4a9trYSWhnQL2dSHT0+kHqJ/8GipiUNP/yAC76dRpDVR3xtYNr73iw0j
+hyDsVjihTD8JBebs3axnt+Bc+FwoCCd6CVcsggfGUNhu/N5LS78b13PcaRzrUNjU
+Eh+8cJvMLst+UQzePlyazzpn7jjN3KsBzWUkbnXCtUs2qRMn8f2gZqliDo7JSFvy
+WtBSCYpikOivuJSQUlrHQ8NaXeddyWQzLY79
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/intermediate.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/intermediate.cert.pem
new file mode 100644
index 0000000..98e1366
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/intermediate.cert.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzELMAkGA1UEBhMCdXMx
+DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEQMA4GA1UEAwwHcm9vdCBj
+YTAeFw0xNzExMDIwMDM3MDZaFw0yNzEwMzEwMDM3MDZaMEcxCzAJBgNVBAYTAnVz
+MQ4wDAYDVQQIDAVkdW1teTEOMAwGA1UECgwFZHVtbXkxGDAWBgNVBAMMD2ludGVy
+bWVkaWF0ZSBjYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOOxzve7
+CG2P9SvKfXkJVTXkj4y79JSZ77Kud/TiPfDbHTqZWKuLTXkOCkCCxfpuJvWXnnj5
+1AeLCdKx9hEwJQeU23EXDt1K+RsRyl09SXtPNnJnqHD1mUHRQR28vGX5ctrQzK8J
+Sa6/mHW4bX8ol100npbgVMDnM4IDfLYcsv4BXMICGkSHOW6Gn0zJaeHzRVPpmnK/
+0k/GQAcIrU2sZ39kVlVQkWq3HJC28cNL/P04hjh4gAf0evo/k9VrEtxPWYMfiPDt
+kOAKueoPv/VTA/zL5t8lyzfhrhxvsJxFg/klapPXK0gLLbhsHyOhnkbrzvmSR4Rw
+xubYJ2dDK0DKx+BIZqlFznjP9BvOtvtuVVMyqg9cfgc7J/OjvAguO0f93MLSfIWP
+uISqv7Llt/Blvy/xI5owvOKVc/hm3d+5pqjWBC1HkVwo4qugpWmM49dFWl4kc4c7
+ayYUjTmcgoj1ZR89w4Off/bPd1A6gXqSkw2VQfgFF+uOos84fP1V+zPWhp3UDY3P
+bFeJtuTdv1gR5w1jCIq6xVJ+UsyDZBaYP7yBBRiNzS1/yXJpnXrvHmDfUeQHLBPR
+N0nbMjqXJ1dVpZwydiI0Qx9DnJtOaq/spUreXr8+PU2jeQdCCAN21MB1umr2gZBJ
+8MZBStTgE7SDByfGmGfp7B5/s/r4O/rNc4WzAgMBAAGjZjBkMB0GA1UdDgQWBBQq
+KmIJiodwlGn1afqWl8X8/h3vhTAfBgNVHSMEGDAWgBSVhLUME20woGVjTdPFUsY3
+TQNKGjASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
+9w0BAQsFAAOCAgEAvzLu/Jc8DlfCltVufC54UZ8DVwUfxdGapNBGv4icrs1wMV3S
+xqdaLO+vSp9NeEufi724+/hj4URapW9kSt2TMD7BNJ61QSATZFJajxTFgGa0Zz95
+RBDw8/b5Arz/2pOF4VX+FJ+wqHvoH/2A0T+fwz8hLORhxZHv/cUN6kif4FKCwryQ
+s89e694kXkEiJfquvu7DR9hYCLOJwzMOOJiTnjz3hlQg4WGu7Z8ZvqzCM+how1hr
+nYbUx6a+HfoUf79AHJB0N1EsEEetJ+omvTdrrayCvy1bHA3QgHlJ28QZIJ7MzX9E
+n11/xQ95iTuSp8iWurzjTjbrm7eHnGUh+5QubYLXOzbqKzNZu72w0uvWv6ptIudU
+usttltiwW8H9kP0ArWTcZDPhhPfS9impFlhiPDk1wUv2/7g+Zz1OaOb7IiSH0s8y
+FG72AB8ucJ5dNa/2q5dJiM8Gm5CbiVw5RXTBjlfTTkNeM6LBI3dRghpPdU7Kbfhn
+xYs9vnRZeRMJHrcodLuwVcpY/gyeJ0k5LD6eIPCJmatkYZ122isYyMX8lL2P5aR+
+7d2hhqcOCproOrtThjp6nW2jWTB+R/O2+s6mhKSPgfbY2cdky1Y9FSJxSNayb9B8
+eQ+A29iOHrGVAA0R/rvw119rLAYxjXzToM28owx7IyXKrBaU4RMv5yokgag=
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/localhost-1.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/localhost-1.cert.pem
new file mode 100644
index 0000000..f15f1cf
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/certs/localhost-1.cert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFFzCCAv+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCdXMx
+DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEYMBYGA1UEAwwPaW50ZXJt
+ZWRpYXRlIGNhMB4XDTE3MTEwMjAwMzcwNloXDTI3MTAzMTAwMzcwNlowTTELMAkG
+A1UEBhMCdXMxDjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEKMAgGA1UE
+CwwBMTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArRAy0Nim9P883BAisXdFoKmgHGTtcLH/SzwkkPWTFHz0rHU1Klwz
+w8u3OkRyvgoQp7DqkohboNMDwg5VrOOcfKwtM2GZ5jixo+YKvJ25oj8Jfr+40baz
+nyWTmOcfoviKrb7u2T9BPEEz5og+lXRDAsTFATGaQDX2LN3Dd9KIw+7sWY+gc3Zi
+13HHaWYhtmfJjzFbH1vDxHKCdSdgtPyEhqcJ4OC6wbgp/mQ01VlPAr08kRfkC8mT
+TS7atqc410irKViF3sWi4YNPf7LuBrjo75FIIOp+sQgZE6xwOuZ/9bT2Zx/IUtCC
+TqzVgZI0s5NVlINtWR6eyyxQ1uDKTs4xrQIDAQABo4IBBTCCAQEwCQYDVR0TBAIw
+ADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2Vu
+ZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUDE8pwi7aELJjvyNT
+ed81/KIgGfowaAYDVR0jBGEwX4AUKipiCYqHcJRp9Wn6lpfF/P4d74WhQ6RBMD8x
+CzAJBgNVBAYTAnVzMQ4wDAYDVQQIDAVkdW1teTEOMAwGA1UECgwFZHVtbXkxEDAO
+BgNVBAMMB3Jvb3QgY2GCAhAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
+BgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEA2cvXxJw120Z9oWXyGwR6CH7TcXoy
+1i77B1M5j0Krvkjh2/MkEU+JxpZcrhAgZODK9wMPeIUIpJNw2t6Hg+vigpInu7pY
+MXR4IA5XLnhGV/hueXa0JLia5FG1TISxr4piW6Jd9P2pOt3ECm3Url/F0OeFF/74
+jGaAlWkbhqWJ9M7Gd4QP2wUNm0P4CwAqS9DC6dnMz+JXTakEUirOpmq7U8UKT+5N
+QS1K4WuH671n4MiYye3+UoRYt4zPjOzN+QxzvAMtkUBspPmWD6txmD5tKUYDECqn
+0sSbY6ytD30OTHIbICFp40arOffmEEJSriL+uQNPPmvqMxX1G2kUFGm15NLPs8Xa
+J7ChrAaJzssN5J3myZUbDfCuxmTkWg+hGvGmxLraVNWc3fzKFmdszSkXrGIdf2HR
+gZeFI3w6M4Ktx3KctXlsjwqQTYZI/WwLOEpsrHQBPBLQhISyNw4xjZ4MxK8SFZuQ
+IiGps/do0eEgeQ+o3gD1dIXt8YxFIxrgk0pzJONpXGgv/cZrukbLNTBdkTSkIwtx
+TXKdiJbO17H24MvW+UxFdsIoJXmfQZWdQC3p+Dl0iP+K80aI6WbaysmToHuOi216
+e49nmiM72Izul2zmBi7Cq2nRQbHAETsFfqC34FzJlx0aP8WS953IBD0jNi1BB+AX
+BxwiZ1rPjeMvekI=
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/private/client.key.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/private/client.key.pem
new file mode 100644
index 0000000..d8a2163
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/private/client.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAwquL6gtPR7P9xJK76FTj8fI5TSJa3cAMt1p6CmessjHQq7nQ
+6DWLGVi4XIt9Sc/1C3rXupOe90Ok4L0tsuVZH78Wn0EBmBH7S4IbhU9P+aJ9mcig
+epj1lnxWqoVblgeJYKMOOwAfpAKUNMWDSm+nCfwE+R5d8d8cfA41Awq1jTRjOVpi
+Jq6aoKfs791a1ZkZde3kFrNVAVjC06GgA1lZd3sHf94hmLeC+xJztRXVE9e+7dcc
+7nFDH0t5DIKYBAklsHg77mZa3IK4aOZew7Lm6diPoMnAzXh2rWpJU6RrEE29gIkJ
+BsF8CL1Ndg9MzssCg6KBjoaiVt5dJ+4TSEGCOwIDAQABAoIBABECKAFU56JeKYfp
+Qh20fQ4Amd0RaVsCkpnaf9s037PaAl9eptADDZozVDhRv6qZTtGn8/1LNJJqCJfS
+L5H30+egLHvRlDATMh+QyJLHMTegaNTs4IiVoK97QZ84c54SHoCg/ndNNXaA+y35
+K9VvF+sZZ93UN2UQl06Hdz5Cy0YA7L5HIIH3Ezk0ArAw4AarLil5mv4yEz2ApZhm
+Tw4I4yNfxB7tZeP+ekNg0XXRL1quA0tGblp+A5fAFfVMDplqqB2d3/KxPR9FSEOi
+4PzBZ5Mq2wQBPIaNog5um9qkw6VKxjl5sQGhP1GGTA8iZqR9iM2+xh57xdCZm3g3
+jcr+aPECgYEA42mXTsF/4oBQtU6hh/sOCMWHhxAPstKpQHFMKGYLHKEJ/V1qq0Sd
+d0kswAYCmH5G9ookzu5p7pNf0hUUHO5EwelpSZ3FEmtIM+oBwSnDk3vGuadYXN5X
+fPuVUla65B1F9SSwapYNBUAiRgrY69Knca2rkTSdcZQaBuWmo684UQcCgYEA2yRE
+P23I/9N6AVhKB/zTRtil1AxnTW8o+j7AE4q1o+xly7DS7DT34INaLKLiuG6ylV1F
+UoTiqmWqH3A7m3o3Id2AnVf/oDoKV78LCXRF3dJJWvzrPdob2fLlwyjgqXYvmD3O
+UH/OFY2blYcAHOYib1Y1AAhHPlXiHA52BYZtnC0CgYAVjjitWmII0ijURrPA8+cM
+pcyG3NrgFF++n/6cBbAf8pPD1Er8GPDkEaeQPAGa+r03OTjr9GVOG+IFQ8I4S81w
+o/M66x129XxOj2vDJ3ZGUIExr88MXnbkfeRVfasRXET5S5T9RWPOj5mwEe8lyz3b
+5J5SkS4rSeJ9rN7yvPUVmQKBgAvrrB67LRzldxSNpfFLSn7nGBYx2oi2zEbYlQA7
+ImhZWqw64S5iLz2yR3x4G9cmhmZjnXrAqcfVIez14PgzLL6V2wI0ID6qCZf+V25b
+OdW4M69UZMOHks5HTUJRfe8Z87rXWdq9KQu5GUaIAnSP/D2MNfPbf2yfpV4bV0Yz
+qtC9AoGAD3/XXaeGCdV5DPomEmehp84JXU2q/YECRvph46tr4jArG67PCvx2m84B
++W6my4Yi7QJcW4gC0gsdAuxbJl4Y7MCZBnTtNIRCRnHEIciKITJ/+brFln5QUgyn
+WnXEPN8q7VjSVXGrljFuLWkzi2Vh8iZDgourNfW+iYDGCJjx1H0=
+-----END RSA PRIVATE KEY-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/private/localhost-1.key.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/private/localhost-1.key.pem
new file mode 100644
index 0000000..aa83f1a
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_1/intermediate/private/localhost-1.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArRAy0Nim9P883BAisXdFoKmgHGTtcLH/SzwkkPWTFHz0rHU1
+Klwzw8u3OkRyvgoQp7DqkohboNMDwg5VrOOcfKwtM2GZ5jixo+YKvJ25oj8Jfr+4
+0baznyWTmOcfoviKrb7u2T9BPEEz5og+lXRDAsTFATGaQDX2LN3Dd9KIw+7sWY+g
+c3Zi13HHaWYhtmfJjzFbH1vDxHKCdSdgtPyEhqcJ4OC6wbgp/mQ01VlPAr08kRfk
+C8mTTS7atqc410irKViF3sWi4YNPf7LuBrjo75FIIOp+sQgZE6xwOuZ/9bT2Zx/I
+UtCCTqzVgZI0s5NVlINtWR6eyyxQ1uDKTs4xrQIDAQABAoIBAC56mDswxH4uAmlT
+yA2Da+a/R6n4jTBkDZ1mFKf93Dd3a7rZa6Lpylk+YAI9GdfiGiD/SbB7AKjLo0m9
+0dKx+ngdQbJ39v42obbT9HQ9o/poFaO91+QyvkDytZYuFHgPaidJjRo5e8qz9D1o
+v+4hoFGhCQvOB5BRLcFU+cc3etWr5t61sNL/kKCWEDd+MWWsOCHpdhEoWC+o25pC
+bhD3FG5xoz+8zL7WdNfke/4twfKoBJ/kq89bfIkl8eKpg387WBQY44RJF7/zVr7a
+9dsUuW2y/wVXslCHChjSrxhRlOyy5ssv3EgKh8gPkZ+oeKuONqAGw27nyKyvpjxS
+i62K+WECgYEA4oKpIS2D77RCC6rpYIK6KYfbUcNSOtHFvcbf0RH9Oi8vSRYum2ZA
+/ITdWSFgWkhT6iOSPuvZlu/EvueWDgNgW1ZVsTMFeapz1+Jwk7JRoBKF1dUEwELh
+jdAswdh0MLbgBYs6NXtVVkeK2ocgZtosmt1PUktl566NlyIyhOjH6vkCgYEAw5g0
+cteTpz+noKsfWcbnjyesuQy0caICfZIE01nKv9rKTF8BtCO6Qxj10iM2o00jW7Vl
+tZa/igjuqvozXAHBI3xegtrWV05urkjj3FB/Pyuqsx3wxhAdSNchQjdTjwUBQEzp
+3ztGSlDTRPpijnpW28lg8Kkr3weryaHvl0xM1VUCgYBqnTN8QU8rgT3g/gYw/fcf
+2ylY98V5mAkqBTSN1JjLTTBFh2JSlLOb5/HDpRkUBZ0xxKJuaVaWW67QaHLRj7dH
+5oAZErnOBXPXNmbkrfcLkAxclJJS6Gf/9u9KIla2Iy2YjmrMh4uoO65Yo2eV4bVD
+A031nzWM8jUE4PzEYEjRCQKBgHDdTj6KiQg0Yg0DUabjcNEZasCpRSJhAyDkdmZi
+5OzKWnuxQvFowF1hdM/aQ/f9Vg7gYJ1lLIeBWf9NOv+3f3RzmrHVh2N/vbxSETIb
+PSH9l5WeDEauG8fhY66q8EuR7sPk3ftTX98YPqEJ/n8Ktz5COO8GH2umKInEKNXc
+UGW1AoGAfENy7vInNv0tzFWPSYdFgesvzo7e8mXyVO8hCyWvY3rxW2on7qfLF3Z9
+fHjd7P9gULja0n1kvmxwUC3u20RrvpY59F4hfi+ji2EiubS9Yhszd2e1CLeRMkln
+ojDjnflN32ZbWVHX/i6g3Dabq9JOD0FsOaOlriLMuofdA6jTUFE=
+-----END RSA PRIVATE KEY-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/certs/ca.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/certs/ca.cert.pem
new file mode 100644
index 0000000..212b586
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/certs/ca.cert.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFZDCCA0ygAwIBAgIJALhSfZ8i0rWTMA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV
+BAYTAnVzMQ4wDAYDVQQIDAVkdW1teTEOMAwGA1UECgwFZHVtbXkxEDAOBgNVBAMM
+B3Jvb3QgY2EwHhcNMTcxMTAyMDAzNzU4WhcNMzcxMDI4MDAzNzU4WjA/MQswCQYD
+VQQGEwJ1czEOMAwGA1UECAwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MRAwDgYDVQQD
+DAdyb290IGNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArHaQ3uyp
+wVaVPZDYvy/EJbnP7KbZNPBvKpQCDEqg9B2TPaC8WVjiqT6I88c+KcGuB8nADJdk
+o10iZC5AwbrXK4ELSCOvBpxYI5sjoFcv3lZ/oe4e650QO2L/ADmtwLaLYK6rZkwW
+Sd90yCGF7ZOTZTJZDwmWEl+ohi+2ow6sRMHKcSKUNfx9G5BB7TOzoqUxqH+moEds
+YpjVMEcKzQi2FmbRd+8Dlg2eGqA2V4faprGQwoYz8NqJZGa/KPpRvXE2VjSTDN6b
+rJ7mmui6eYN53mZEBRYogyoQHdFXhK02FgyoPEgR/wQlLLbQ+xxOcv02YsOljtza
+hl5LjeNUYPMjyhef0QpONp+5NcFhZf38DsSq5EWZLLxPScxwl0lBQkJTjo5ARuFl
+Mrv50RYrLwv4ImsiO2ftE7gAX4vNsgcixnCHd6rNzoGimf1+DSvDVJ9ujWo7HPN3
+7ONuoyjsU4mUJJpYXs8zHx5WSxaYiPJRcmG3LjcU5/A+Fs7bkqSrlEjJsG29xDrO
+vKR7hH+m6MwcIcXSh9wjjAIvHxAALdU9xaYE3hmVkoxew1mRBsYq34h2vpwGOY5r
+0njRQyGGZnVa8qkQd6P3U5fcvLOM8v9QImZqRDS2jAGZXYruo/RIgJpklVX7ZY0+
+CnGdz4YxgLyOBJCDu3aEgL1oON3mg2SsrVMCAwEAAaNjMGEwHQYDVR0OBBYEFOBO
+9R6yEY6KOE+aSClwD2BQtWXKMB8GA1UdIwQYMBaAFOBO9R6yEY6KOE+aSClwD2BQ
+tWXKMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+CwUAA4ICAQBElio7lLZ2eNt0tDPVSkChksc0IJ2/DpjYjqJzqxF/jZ2oToaAn2Er
+9iHl8ggTLB5WrTQxO1ev7qOwQsk9hrgvZ+EQCBTbyDyfNwVjgftH5jdQGdbyrnuJ
+yaks1mnd8is5ZZQmmQSd7GVOMTYi/7yH0xI4DHQ386dwnf5eKdoOI7yrVufEMxRx
+tB3Wv8KrX4z47zsIO28H/O0T26oUkrd4PEqFwDa5HQr6iG7QQgoGD/DPLgbBudlO
+kEos9fmXxiX60RLziKCE/DAxI3YWPvG3WhIWnVj22Oz6apz2pYWpOKwlaihNYrhq
+8xc02vIFwKh+t7D+wF4KHfduyMJ/wKVc5dzpNbTgkZePPKSB7QgbsMeRqbdPoXQF
+pMuzfj8VCWzpqBeHqE/adSCZhzeTrnyiYavF4T2vkSC5KJu+MHmbZ3nU9bcnnEy+
+24oEv9cEAiYNkvftbD+5ByEtkcBB2uT47sbiGrAeco+GxFGUVqi1IjObqrkIrPzV
+OjQhTZV6qgYCOuniJiGfoiMeHqdaDybpqo1bIrxSlvGRNcVoOsKt2/KP1DzW4ARZ
+hoRvayU2apHz/T5TAailqAW2MsrjGRaVHQTmeZKag8CKtAcjWzui0J2DnfXxUMn8
+R3ruFu3xJduOT1VghT9L9udvX9YhPCIKVL9+B5eFX9eMV6N7hUnVug==
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/client.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/client.cert.pem
new file mode 100644
index 0000000..b6f4280
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/client.cert.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIExzCCAq+gAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCdXMx
+DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEYMBYGA1UEAwwPaW50ZXJt
+ZWRpYXRlIGNhMB4XDTE3MTEwMjAwMzgwMFoXDTI3MTAzMTAwMzgwMFowPjELMAkG
+A1UEBhMCdXMxDjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEPMA0GA1UE
+AwwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyxZFTLqv
+Gd9SpFAykyRyLQgHcR5hgD55mz+9fl1OfnMoAc7yTdPVLksDLmeFUlxcvCtLHysJ
+klIBX62c6LzbsVcfLg/DPJlQxFnkhJCRKen4fp7x9h62qqJkDFVXsiEFza9L1lsN
+4OwqU8i4RRgZ/xggM/s/wVBtynioeW9QADNmKZ1n6HVKkYwdOynbFSggYfFrL3HL
+54bC9roZUETin0G5wZ9QU+srgivT0a/KC3ourBYHXAI40iHuuOBf3syDVJ6xId/r
+3UO3qkiQ5q7pwglg+8Nx7Q3CFtGZY3ewxSSSDo6BOyweGYMsBaxMO3EyTqecyfXn
+3n4XPqwmDalWYQIDAQABo4HFMIHCMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD
+AgWgMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBDbGllbnQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFP2bodoNQ1tCNEOALPnygGMUfNI+MB8GA1UdIwQY
+MBaAFOWzLd7eBJwSNbzRqNsD7MQDCHg/MA4GA1UdDwEB/wQEAwIF4DAdBgNVHSUE
+FjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwDQYJKoZIhvcNAQELBQADggIBAHqUuCLt
+olOdR9p/g+KgGPnKuVgMn15Wc2VLCrbbl2P0fuCcNWmnBKqHHgQ1EJEpgnQ2N8m6
+tOGucX7IAzlZj36RP4lN3gZqFRSO/OiTOUYpE6Uv1hYRxeMzAYo5sBdCiiypjV9z
+H0Ew5NuWRf2/0nFWoywB9ktHcfD8lRFI3o8zUFXmE2JSUPQtKhW3tBkPPjYBlgzD
+RD8cq8dVK9P7i3tUENP+MNHJToNLFBqfA9De6bKnhCWHhZkfB0VeeSm4Ja9HkCg/
+DB+PAKMfbLCH5T8gCpEWxNlvj09r9mn37fNjtJPO/goAcNZNO2AURmb/ZQ4ggdry
+xb6lm832qplMUMWx//Ore0faEodlEc5d2kEtmcjj79gAypcLmm74q7CPt7xmniyd
+XvNT33S2tkh4dSirpCVwq0xyqOP3ZqTsTjudTveTBaTZNhTbCjDbaV7ga47TcH9/
++OZ3fQKjt2LAC6162wgEFZf10nUgaAXvSlI74gru93vEwWd8Pd3sWfGwuAFX3oKI
+JuwL2kxEuoZQmeRiVJu6KQb+Im7d5CIoWViDmfxcSDJfdtSePTqmDURIx87fw14Z
+XBWJP4PiK5PRmG/L0cGiDckmDKm/MuD13Z2I/NMl81GNY/q3WY2O7BmddPpAG5dr
+sc5hOqA9+jX08XbxKnfBPYllK5skYMkFH5tN
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/intermediate.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/intermediate.cert.pem
new file mode 100644
index 0000000..4305e53
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/intermediate.cert.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzELMAkGA1UEBhMCdXMx
+DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEQMA4GA1UEAwwHcm9vdCBj
+YTAeFw0xNzExMDIwMDM3NTlaFw0yNzEwMzEwMDM3NTlaMEcxCzAJBgNVBAYTAnVz
+MQ4wDAYDVQQIDAVkdW1teTEOMAwGA1UECgwFZHVtbXkxGDAWBgNVBAMMD2ludGVy
+bWVkaWF0ZSBjYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKuM2iFz
+CCKmbs4uLj/8wjtxf+jFmkwzN4Pps4hqJ3NJqWB326LhzWyqM4WiTAJWE02wSJbS
+16RPfbjkVC77OI4+PUdwqxU9vNAP/95w0h6hBSFtkysuT5VVUt5jiY7wnUKgqTCi
+MYhYOl+HEP32O4cnxAazkUKKvtyrd4/PvejJ9zz+iYexRnaaGfOFR3co7jQ5QKar
+oK4UgJC3mVDZQEMBV0oljkpgVQMAVb4XQU7e+o25JOUkOoK6LdK/b/95khR0jTyD
+OBLqd4mCEzxNi+jZ0jLTLPk0c+DnGmRfoNUxnFb40R8QnEIEKwf+JKyl6p89oqOl
+pvIZFLZlUWIS4qL+993l1SCqPkWJOAdTg+s/Zh6DeAOhrUn9/wk0aQwZrK7wQQLJ
+4GGhxC/FfuUGsLqZszAVkP8jDEWnzhN2rw3V+C7v6lj4qHhUwqGHuYzCx2Hxl+B8
+UyBmZb9gXKVUtAvaZjaL2PDj1ZAxT0KVMlw1ZVrZu45OsHNQuBx/4uIAt6Rga8yt
+av1lsq+hFqwI4bU/oZC/oPMacOsB4qEkAA1101WjMc5bg6JOPWobwIqmUXQR1WJE
+j30e99HCpk1Cc2+9sUCzNu8KvU5kUY2K90zwqProvj5IfMuDetAVXsEjgW+ZqSho
+UMIpJ2M/hzAFl8Z5IRlG+YNfZNXl0FqJ5LzLAgMBAAGjZjBkMB0GA1UdDgQWBBTl
+sy3e3gScEjW80ajbA+zEAwh4PzAfBgNVHSMEGDAWgBTgTvUeshGOijhPmkgpcA9g
+ULVlyjASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
+9w0BAQsFAAOCAgEAOS7DtliOUPcVosRfyx9dHcSUc3O+uY8uKjSHhdIrxDJm4lwP
+Q6lKg5j8CdMVb+sDQmyBkqQIA/6E13corP6R283jO6W8D4A8kjOiQWpXfjW6OcP3
+4rrDEWhCdeLFSNJIYOFkr2qWJpI/k0VpyDnmY0YluS5WbNjg6zTzGelzhFbV7/S1
+cteNAZD0vHD8NmbLVDJjjIY3E/iwzoUzBncLYbDwqyVS1g6utWdSy8LEJxzzqqWJ
+pBKlNYILAdh8efBgvotafaxsn2nfjmVmekPn3KcQZuE4Kzv1EQ2PrHpGeJKwh6up
+YBL2tav5cAki8bWoGPr2oGmWUf9L2tB57SdWdaY60ifzmQaeGiWPZBSmAz7PRSrz
+sR9SMIkBfYVRxXgWwlvr8JYnd2h/Ef5K9fI32nGfje+7/0kPEjNyjehri7sV4Sjt
+zzkDiFO+JklrRuLBPMFYOokq6Pcko32FKlE82pe8QkMDS8Sk//9PqCTK9ceB7y6E
+NYLNBW/X9SAw/TR5kdRinHHgHyEug7N4+DCU3lU1wl72ZjoiGE7V6c2AssFC2VcE
+E+WYxJT1ROJ1/5+U6BKdaIpTwMtRIFRomOEI66iOwOSEwqLIztkqxwpQ7THraWKm
+2W5e54u/efapIDcQFnP3E8r7TD0PdIeU6mD28o0+WiK3uL/OZpvyKaHPeFU=
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/localhost-1.cert.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/localhost-1.cert.pem
new file mode 100644
index 0000000..2850e42
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/certs/localhost-1.cert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFFzCCAv+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCdXMx
+DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEYMBYGA1UEAwwPaW50ZXJt
+ZWRpYXRlIGNhMB4XDTE3MTEwMjAwMzc1OVoXDTI3MTAzMTAwMzc1OVowTTELMAkG
+A1UEBhMCdXMxDjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEKMAgGA1UE
+CwwBMTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAycY/7H1xk3/XHZRopULV7YsOzPIrMG25zoACbpDZxjS0I+2r1c7V
+wnvE8TszAkloLi+Skku5CYC7IvVEEEuKuIuV+8M48FJEwlCPge8LPiy18C+npCEd
+fgDzCV/O9DfJj6UaiCUayVE7UujXoke7AlKQEJcqvnD/CoTv2Y8jV1A6mPf6CTEI
+Sl1BMeFSmeFyvZll+xJ8Up1KfQZxKhtpP1s/rp6ZNlqSs1LM5+vcDHHZ6COTbq7t
+2vvcmGDTqeCLsqicBg1kJyMPRtqa0bNPj2bcVtcK0Ndfn6eL2hi+EoBy2nIXi6aG
+PpXf85b9bCLd5pZI80nHzFlhdvV+SxqrfwIDAQABo4IBBTCCAQEwCQYDVR0TBAIw
+ADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2Vu
+ZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUoYjECaDz/ZELru/r
+jfTB1ShlVrAwaAYDVR0jBGEwX4AU5bMt3t4EnBI1vNGo2wPsxAMIeD+hQ6RBMD8x
+CzAJBgNVBAYTAnVzMQ4wDAYDVQQIDAVkdW1teTEOMAwGA1UECgwFZHVtbXkxEDAO
+BgNVBAMMB3Jvb3QgY2GCAhAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
+BgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAiiR2knMMym4O+3fD1KlYSnc2UR3v
+0FlRVAsr8wvTlVjJhx7DbRusBNJHWX66mUgK9x5OLnhyvyqlFVhR9AwlnxgfLWz9
+nnACeXzcjQZnKWFQWu8bJSC6Ene6rd1g2acK6SOjxavVbj7JVFnmlHF/naZUzvMl
+mJivYta4k7ob8UcX0I5TlJpzglU3UHyyJd5d9zhbF8wqbBq63zR2ovWci4pYCg+F
+jYcTGYVZJti3SHO+9/EqTC9x2KDNs3o0+rreJ3GuoonkInKZMQQZJQ6qILvkxlhT
+jyU5xlcaJ+0tSaiFK3eF0nXIpFYdZbIHYPCdLjh9AZ2dkFcAgSa/L8+tsVt60k8D
+HTO0Hz6dW5D2ckeebZvz5LACMN89gVzrc/rVkeg7QmpSbjkTSLC2KJS53hJzWcEI
+3KB73B9iY+ZYytcYBTYLizsAxd5g7j9z8UXrmVQ4mWbh2+xKiG+9aVOzCZ09AYi6
+WVK2aRcMQshgkkqPOloN9OeQNCE8Exf7N/zHsBhygorJXoD/PFgnV1VZm8xkOdiJ
+zTb3bpGdmL5+bzzS6wP8Q7pGZGYdlnB7JNO8oMYPPtzX8OOx92BTkPnqJnnRWTpR
+SjMEEdQe8K7iXxejQkjaAq5BlwaAOjCjPTqYomECcYjC0WaXsmrPcnZwSqpnHZZ2
+OiINYJub5cvBLNo=
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/private/client.key.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/private/client.key.pem
new file mode 100644
index 0000000..a4c5fd4
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/private/client.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAyxZFTLqvGd9SpFAykyRyLQgHcR5hgD55mz+9fl1OfnMoAc7y
+TdPVLksDLmeFUlxcvCtLHysJklIBX62c6LzbsVcfLg/DPJlQxFnkhJCRKen4fp7x
+9h62qqJkDFVXsiEFza9L1lsN4OwqU8i4RRgZ/xggM/s/wVBtynioeW9QADNmKZ1n
+6HVKkYwdOynbFSggYfFrL3HL54bC9roZUETin0G5wZ9QU+srgivT0a/KC3ourBYH
+XAI40iHuuOBf3syDVJ6xId/r3UO3qkiQ5q7pwglg+8Nx7Q3CFtGZY3ewxSSSDo6B
+OyweGYMsBaxMO3EyTqecyfXn3n4XPqwmDalWYQIDAQABAoIBAFhIOR3OtVlw3BLz
+jdiq6jsrF1kUFNxTzDcxsSUiWIHde1G17Vzpre0uzJY6iBkyb1mZFFHbOpDxtwkp
+hmEh3/qqXbJ/RaatGxAP56e81G28+LnKTHJqDYwFhapa2wFjG4u7HSN0d4cEAq5j
+Pb9DZ+GdUjpmiON3HBL8+ne3bLZ42uI+DSVe8d3irbqg2rqsiANf0gdimMW4nuI4
+rVxf8HrY43PdQn/Vby+7qLRE3tmIlpbTqJGRtWRjdeBBI91APCrRljjXrKqT6Zpa
+E6Daz3YIQvXkIT0q+WkeN1VmQbtRnk7kRsPNp15kSwpHfmv6o/vkO9OUb1n71P2F
+wnB0WDECgYEA8iltnKxXnjqwZ/vzIWzcd94j+mdZg/K2/JCOqjwMvpSGCvx2zUmq
+Y2nxO2K85AVeOm/Yt87SMODB6AQ9CsrVGEUAzzacvCJDb8oUhaOL5gypnyvZiGCy
+snzXfgB+v/xuGekIjs2y7E8h3GG40j0aNQnUY1Fuc6iaeJG4BtjkuQUCgYEA1rE4
+DrTSsUh3hLYQusIHZR8Lecrrd4QUZSMKLkWjobiSTw3m4mglx1s2G4eZ3WuzOyFq
+Dp3/b3yfT8prdPBGA6shHNFf+1TO1q1/pIt15dc3sFwxMkuunai8N4QZJRqZLbYq
+FkNFkZ20hFHcH/NHDsAsRL/0tJdEmJ2ruP+Qdq0CgYBsdPGKwgVb8J0hdU4nIkJ7
+zRoABFmrJwGdjIDY7Zwnnw2JzhjHSL7vV3ubRVWkKmNReNZvPEoXahJuf7d3JfDa
+tczvAV6hRBc/8hnO4Li/h9xQVatP0T83gYJiBIbAJaaKJDyY+Lex7p8TvRCx2Hvs
+VUKyWL5HPrQwW9M3/dwyoQKBgQCNQoPA4Wcz8Jt7PZQaXaoh9eBGHab6t3P366s6
+MOXudZQG4f3FgINC/ZfHW1x43PFL+btfrMOyJkxoYqZ7hdB7f3DFFlpR80Y46GVw
+7bYAKbBhoPdZwYQ+BhT5bjhhOnQJKK/egBrZKevpmDb+6sIZSYaXIbovzMv8otmn
+WrhB7QKBgAdl+KYBQULCUBp8qCQH5sAQoWErpyuD2FNN6LGknpPqn4DdujvwEP0Z
+OSvbauLkI0Qc9/MezKPTeYXlFqdbpItwyySJsUkiI3HhVYlBgDkZ7xb6uHIH5E6I
+bKgIW5JEf5I7Eu1iurORkXxCCGMkiQmEs4X5kSXXRYgXfNgAD0FX
+-----END RSA PRIVATE KEY-----
diff --git a/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/private/localhost-1.key.pem b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/private/localhost-1.key.pem
new file mode 100644
index 0000000..8cba174
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/credentials/certificate_hierarchy_2/intermediate/private/localhost-1.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAycY/7H1xk3/XHZRopULV7YsOzPIrMG25zoACbpDZxjS0I+2r
+1c7VwnvE8TszAkloLi+Skku5CYC7IvVEEEuKuIuV+8M48FJEwlCPge8LPiy18C+n
+pCEdfgDzCV/O9DfJj6UaiCUayVE7UujXoke7AlKQEJcqvnD/CoTv2Y8jV1A6mPf6
+CTEISl1BMeFSmeFyvZll+xJ8Up1KfQZxKhtpP1s/rp6ZNlqSs1LM5+vcDHHZ6COT
+bq7t2vvcmGDTqeCLsqicBg1kJyMPRtqa0bNPj2bcVtcK0Ndfn6eL2hi+EoBy2nIX
+i6aGPpXf85b9bCLd5pZI80nHzFlhdvV+SxqrfwIDAQABAoIBAQC022161aoTEtjH
+m7n8v56vUCCRFVQfEYsljFohrtZ0sdLyDVwjxkSWEYiizXRYTWIDXALd/N+7o9aZ
+bAx5Kq0J45wpUYBc8PDO15T6W0DRlxPxWVXDaSddRQ6TTXxcLREPH2dbtx5+asBo
+/Woi/Haki0q0hDr8/p2sWSH/+SwtWpOezGVlrWrkMeIhlBwHZfdHVoZvSx65Uv7x
+WU07vsjrbXNDwf+2fmklAQrzhedCeh8loGyjtN3cfrTjrE1zqpEsHnlZcJxe6sRB
+1nOqpoUnpZXklDDIYC8EmeubmDJ0jnXOQCDDep3MzVcnZGyF5E/+szaa1NL70Ayj
+rbKk1Y3ZAoGBAPy/1ym7Cjl4OGHN2fdkR6iL68ebJozpr+eTSxDNLuBSi5IJxJyG
+1+B4+v1u0RwZ3DjrSQsO5DCbZ+DHU6O/DAJK2CxUED+M+G2kRyffailRQmNzjpRG
+75dIhSkSRYH8vdvEOnGpeQBZwBcCRH/2YUMlZeSfx9fHJhk1nyUxJeHjAoGBAMxe
+k+cBb0zYok+Ww1xTwOdq0PwKj0oDsEg8hOdWc8pH0SlOAB4BI5kmfd1JDMHfRc49
+7tpNqjsPrnlb9xd8l0281Lj2NoVSE5KX1JtsOsKecQsvHH5zRk4eJ3h/mNixpjfe
+79Zc/O40T4rWpQRqhat+WHveJC0/ON4AH4uT0BK1AoGBAPcTioCu6YXYsjVaCJPB
+IhPwBGOylfL2lxDoel9IVWTRDMOMbPkfEHXNjn6lECJKXW//Af6fZg7mPJwN/wN5
+xYGQLNbYrrGRW2HDUBP4YU1WtHGIC3+EAL+BEztdMzmpGuh1YTSvmSvwkMltXA1D
+iz0amArw72lOsz29n3+6FfBFAoGAIpRqMC8k9vq80/yth6TAQifnvo3G2v4uyLo8
+vqv5IaPvNy70hB8rN9G0gEnI99Dgjdoa3SNBB4dKvUwbTgUN0OB/meBHL13I5Af+
+uGGiu6V1eS/6gUbeAX/Gq/PjF99PQareKAZJ4cBGKTbSayHfBjp1nFflBSbqZ13b
++JEFJvUCgYBOs2J2XXamPbI7gu7B2TE9j/62v0SJyoHq2LHMmYUDRuPdPk3eKCt3
+283w+E8XUIFbctaxsbo8msNjjvV22D/Nci3d87aPe8bn1SVto3GnTuwnOpRq3E+3
+wAarqrhiZbGZSCcAkEOk7FlxAwYnCM6paqMxDEMCJ4qChMM42E9ZyQ==
+-----END RSA PRIVATE KEY-----
diff --git a/src/python/grpcio_tests/tests/unit/resources.py b/src/python/grpcio_tests/tests/unit/resources.py
index 823d230..11ef9e8 100644
--- a/src/python/grpcio_tests/tests/unit/resources.py
+++ b/src/python/grpcio_tests/tests/unit/resources.py
@@ -11,7 +11,7 @@
 # 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.
-"""Constants and functions for data used in interoperability testing."""
+"""Constants and functions for data used in testing."""
 
 import os
 
@@ -34,3 +34,81 @@
 def certificate_chain():
     return pkg_resources.resource_string(__name__,
                                          _CERTIFICATE_CHAIN_RESOURCE_PATH)
+
+
+def cert_hier_1_root_ca_cert():
+    return pkg_resources.resource_string(
+        __name__, 'credentials/certificate_hierarchy_1/certs/ca.cert.pem')
+
+
+def cert_hier_1_intermediate_ca_cert():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_1/intermediate/certs/intermediate.cert.pem'
+    )
+
+
+def cert_hier_1_client_1_key():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_1/intermediate/private/client.key.pem'
+    )
+
+
+def cert_hier_1_client_1_cert():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_1/intermediate/certs/client.cert.pem')
+
+
+def cert_hier_1_server_1_key():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_1/intermediate/private/localhost-1.key.pem'
+    )
+
+
+def cert_hier_1_server_1_cert():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_1/intermediate/certs/localhost-1.cert.pem'
+    )
+
+
+def cert_hier_2_root_ca_cert():
+    return pkg_resources.resource_string(
+        __name__, 'credentials/certificate_hierarchy_2/certs/ca.cert.pem')
+
+
+def cert_hier_2_intermediate_ca_cert():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_2/intermediate/certs/intermediate.cert.pem'
+    )
+
+
+def cert_hier_2_client_1_key():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_2/intermediate/private/client.key.pem'
+    )
+
+
+def cert_hier_2_client_1_cert():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_2/intermediate/certs/client.cert.pem')
+
+
+def cert_hier_2_server_1_key():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_2/intermediate/private/localhost-1.key.pem'
+    )
+
+
+def cert_hier_2_server_1_cert():
+    return pkg_resources.resource_string(
+        __name__,
+        'credentials/certificate_hierarchy_2/intermediate/certs/localhost-1.cert.pem'
+    )
diff --git a/templates/Makefile.template b/templates/Makefile.template
index 0588787..3eb8388 100644
--- a/templates/Makefile.template
+++ b/templates/Makefile.template
@@ -215,7 +215,7 @@
   ifeq ($(SYSTEM),Darwin)
   CXXFLAGS += -stdlib=libc++
   endif
-  % for arg in ['CFLAGS', 'CXXFLAGS', 'CPPFLAGS', 'LDFLAGS', 'DEFINES']:
+  % for arg in ['CFLAGS', 'CXXFLAGS', 'CPPFLAGS', 'COREFLAGS', 'LDFLAGS', 'DEFINES']:
   %  if defaults.get('global', []).get(arg, None) is not None:
   ${arg} += ${defaults.get('global').get(arg)}
   %  endif
@@ -1268,6 +1268,16 @@
   	$(Q) mkdir -p `dirname $@`
   	$(Q) $(HOST_CXX) $(HOST_CXXFLAGS) $(HOST_CPPFLAGS) -MMD -MF $(addsuffix .dep, $(basename $@)) -c -o $@ $<
 
+  $(OBJDIR)/$(CONFIG)/src/core/%.o : src/core/%.cc
+  	$(E) "[CXX]     Compiling $<"
+  	$(Q) mkdir -p `dirname $@`
+  	$(Q) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(COREFLAGS) -MMD -MF $(addsuffix .dep, $(basename $@)) -c -o $@ $<
+
+  $(OBJDIR)/$(CONFIG)/test/core/%.o : test/core/%.cc
+  	$(E) "[CXX]     Compiling $<"
+  	$(Q) mkdir -p `dirname $@`
+  	$(Q) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(COREFLAGS) -MMD -MF $(addsuffix .dep, $(basename $@)) -c -o $@ $<
+
   $(OBJDIR)/$(CONFIG)/%.o : %.cc
   	$(E) "[CXX]     Compiling $<"
   	$(Q) mkdir -p `dirname $@`
diff --git a/templates/test/core/surface/public_headers_must_be_c89.c.template b/templates/test/core/surface/public_headers_must_be_c89.c.template
index e2d3b18..6e4a836 100644
--- a/templates/test/core/surface/public_headers_must_be_c89.c.template
+++ b/templates/test/core/surface/public_headers_must_be_c89.c.template
@@ -48,10 +48,8 @@
   #include <stdio.h>
 
   int main(int argc, char **argv) {
-    if(argc == 12345678) {
-      % for fn in fns:
-      printf("%lx", (unsigned long) ${fn});
-      % endfor
-    }
+    % for fn in fns:
+    printf("%lx", (unsigned long) ${fn});
+    % endfor
     return 0;
   }
diff --git a/test/core/support/BUILD b/test/core/support/BUILD
index 9d042fd..69512cd 100644
--- a/test/core/support/BUILD
+++ b/test/core/support/BUILD
@@ -139,6 +139,16 @@
 )
 
 grpc_cc_test(
+    name = "manual_constructor_test",
+    srcs = ["manual_constructor_test.cc"],
+    language = "C++",
+    deps = [
+        "//:gpr",
+        "//test/core/util:gpr_test_util",
+    ],
+)
+
+grpc_cc_test(
     name = "spinlock_test",
     srcs = ["spinlock_test.cc"],
     language = "C++",
diff --git a/test/core/support/manual_constructor_test.cc b/test/core/support/manual_constructor_test.cc
new file mode 100644
index 0000000..714f8b2
--- /dev/null
+++ b/test/core/support/manual_constructor_test.cc
@@ -0,0 +1,99 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+/* Test of gpr synchronization support. */
+
+#include "src/core/lib/support/manual_constructor.h"
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/sync.h>
+#include <grpc/support/thd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <cstring>
+#include "src/core/lib/support/abstract.h"
+#include "test/core/util/test_config.h"
+
+class A {
+ public:
+  A() {}
+  virtual ~A() {}
+  virtual const char* foo() { return "A_foo"; }
+  virtual const char* bar() { return "A_bar"; }
+  GRPC_ABSTRACT_BASE_CLASS
+};
+
+class B : public A {
+ public:
+  B() {}
+  ~B() {}
+  const char* foo() override { return "B_foo"; }
+  char get_junk() { return junk[0]; }
+
+ private:
+  char junk[1000];
+};
+
+class C : public B {
+ public:
+  C() {}
+  ~C() {}
+  virtual const char* bar() { return "C_bar"; }
+  char get_more_junk() { return more_junk[0]; }
+
+ private:
+  char more_junk[1000];
+};
+
+class D : public A {
+ public:
+  virtual const char* bar() { return "D_bar"; }
+};
+
+static void basic_test() {
+  grpc_core::PolymorphicManualConstructor<A, B> poly;
+  poly.Init<B>();
+  GPR_ASSERT(!strcmp(poly->foo(), "B_foo"));
+  GPR_ASSERT(!strcmp(poly->bar(), "A_bar"));
+}
+
+static void complex_test() {
+  grpc_core::PolymorphicManualConstructor<A, B, C, D> polyB;
+  polyB.Init<B>();
+  GPR_ASSERT(!strcmp(polyB->foo(), "B_foo"));
+  GPR_ASSERT(!strcmp(polyB->bar(), "A_bar"));
+
+  grpc_core::PolymorphicManualConstructor<A, B, C, D> polyC;
+  polyC.Init<C>();
+  GPR_ASSERT(!strcmp(polyC->foo(), "B_foo"));
+  GPR_ASSERT(!strcmp(polyC->bar(), "C_bar"));
+
+  grpc_core::PolymorphicManualConstructor<A, B, C, D> polyD;
+  polyD.Init<D>();
+  GPR_ASSERT(!strcmp(polyD->foo(), "A_foo"));
+  GPR_ASSERT(!strcmp(polyD->bar(), "D_bar"));
+}
+
+/* ------------------------------------------------- */
+
+int main(int argc, char* argv[]) {
+  grpc_test_init(argc, argv);
+  basic_test();
+  complex_test();
+  return 0;
+}
diff --git a/test/core/surface/public_headers_must_be_c89.c b/test/core/surface/public_headers_must_be_c89.c
index db9f902..b5a83c8 100644
--- a/test/core/surface/public_headers_must_be_c89.c
+++ b/test/core/surface/public_headers_must_be_c89.c
@@ -66,288 +66,286 @@
 #include <stdio.h>
 
 int main(int argc, char **argv) {
-  if(argc == 12345678) {
-    printf("%lx", (unsigned long) grpc_compression_algorithm_parse);
-    printf("%lx", (unsigned long) grpc_compression_algorithm_name);
-    printf("%lx", (unsigned long) grpc_stream_compression_algorithm_name);
-    printf("%lx", (unsigned long) grpc_compression_algorithm_for_level);
-    printf("%lx", (unsigned long) grpc_stream_compression_algorithm_for_level);
-    printf("%lx", (unsigned long) grpc_compression_options_init);
-    printf("%lx", (unsigned long) grpc_compression_options_enable_algorithm);
-    printf("%lx", (unsigned long) grpc_compression_options_disable_algorithm);
-    printf("%lx", (unsigned long) grpc_compression_options_is_algorithm_enabled);
-    printf("%lx", (unsigned long) grpc_compression_options_is_stream_compression_algorithm_enabled);
-    printf("%lx", (unsigned long) grpc_metadata_array_init);
-    printf("%lx", (unsigned long) grpc_metadata_array_destroy);
-    printf("%lx", (unsigned long) grpc_call_details_init);
-    printf("%lx", (unsigned long) grpc_call_details_destroy);
-    printf("%lx", (unsigned long) grpc_register_plugin);
-    printf("%lx", (unsigned long) grpc_init);
-    printf("%lx", (unsigned long) grpc_shutdown);
-    printf("%lx", (unsigned long) grpc_version_string);
-    printf("%lx", (unsigned long) grpc_g_stands_for);
-    printf("%lx", (unsigned long) grpc_completion_queue_factory_lookup);
-    printf("%lx", (unsigned long) grpc_completion_queue_create_for_next);
-    printf("%lx", (unsigned long) grpc_completion_queue_create_for_pluck);
-    printf("%lx", (unsigned long) grpc_completion_queue_create);
-    printf("%lx", (unsigned long) grpc_completion_queue_next);
-    printf("%lx", (unsigned long) grpc_completion_queue_pluck);
-    printf("%lx", (unsigned long) grpc_completion_queue_shutdown);
-    printf("%lx", (unsigned long) grpc_completion_queue_destroy);
-    printf("%lx", (unsigned long) grpc_completion_queue_thread_local_cache_init);
-    printf("%lx", (unsigned long) grpc_completion_queue_thread_local_cache_flush);
-    printf("%lx", (unsigned long) grpc_alarm_create);
-    printf("%lx", (unsigned long) grpc_alarm_set);
-    printf("%lx", (unsigned long) grpc_alarm_cancel);
-    printf("%lx", (unsigned long) grpc_alarm_destroy);
-    printf("%lx", (unsigned long) grpc_channel_check_connectivity_state);
-    printf("%lx", (unsigned long) grpc_channel_num_external_connectivity_watchers);
-    printf("%lx", (unsigned long) grpc_channel_watch_connectivity_state);
-    printf("%lx", (unsigned long) grpc_channel_support_connectivity_watcher);
-    printf("%lx", (unsigned long) grpc_channel_create_call);
-    printf("%lx", (unsigned long) grpc_channel_ping);
-    printf("%lx", (unsigned long) grpc_channel_register_call);
-    printf("%lx", (unsigned long) grpc_channel_create_registered_call);
-    printf("%lx", (unsigned long) grpc_call_arena_alloc);
-    printf("%lx", (unsigned long) grpc_call_start_batch);
-    printf("%lx", (unsigned long) grpc_call_get_peer);
-    printf("%lx", (unsigned long) grpc_census_call_set_context);
-    printf("%lx", (unsigned long) grpc_census_call_get_context);
-    printf("%lx", (unsigned long) grpc_channel_get_target);
-    printf("%lx", (unsigned long) grpc_channel_get_info);
-    printf("%lx", (unsigned long) grpc_insecure_channel_create);
-    printf("%lx", (unsigned long) grpc_lame_client_channel_create);
-    printf("%lx", (unsigned long) grpc_channel_destroy);
-    printf("%lx", (unsigned long) grpc_call_cancel);
-    printf("%lx", (unsigned long) grpc_call_cancel_with_status);
-    printf("%lx", (unsigned long) grpc_call_ref);
-    printf("%lx", (unsigned long) grpc_call_unref);
-    printf("%lx", (unsigned long) grpc_server_request_call);
-    printf("%lx", (unsigned long) grpc_server_register_method);
-    printf("%lx", (unsigned long) grpc_server_request_registered_call);
-    printf("%lx", (unsigned long) grpc_server_create);
-    printf("%lx", (unsigned long) grpc_server_register_completion_queue);
-    printf("%lx", (unsigned long) grpc_server_add_insecure_http2_port);
-    printf("%lx", (unsigned long) grpc_server_start);
-    printf("%lx", (unsigned long) grpc_server_shutdown_and_notify);
-    printf("%lx", (unsigned long) grpc_server_cancel_all_calls);
-    printf("%lx", (unsigned long) grpc_server_destroy);
-    printf("%lx", (unsigned long) grpc_tracer_set_enabled);
-    printf("%lx", (unsigned long) grpc_header_key_is_legal);
-    printf("%lx", (unsigned long) grpc_header_nonbin_value_is_legal);
-    printf("%lx", (unsigned long) grpc_is_binary_header);
-    printf("%lx", (unsigned long) grpc_call_error_to_string);
-    printf("%lx", (unsigned long) grpc_resource_quota_create);
-    printf("%lx", (unsigned long) grpc_resource_quota_ref);
-    printf("%lx", (unsigned long) grpc_resource_quota_unref);
-    printf("%lx", (unsigned long) grpc_resource_quota_resize);
-    printf("%lx", (unsigned long) grpc_resource_quota_arg_vtable);
-    printf("%lx", (unsigned long) grpc_auth_property_iterator_next);
-    printf("%lx", (unsigned long) grpc_auth_context_property_iterator);
-    printf("%lx", (unsigned long) grpc_auth_context_peer_identity);
-    printf("%lx", (unsigned long) grpc_auth_context_find_properties_by_name);
-    printf("%lx", (unsigned long) grpc_auth_context_peer_identity_property_name);
-    printf("%lx", (unsigned long) grpc_auth_context_peer_is_authenticated);
-    printf("%lx", (unsigned long) grpc_call_auth_context);
-    printf("%lx", (unsigned long) grpc_auth_context_release);
-    printf("%lx", (unsigned long) grpc_auth_context_add_property);
-    printf("%lx", (unsigned long) grpc_auth_context_add_cstring_property);
-    printf("%lx", (unsigned long) grpc_auth_context_set_peer_identity_property_name);
-    printf("%lx", (unsigned long) grpc_channel_credentials_release);
-    printf("%lx", (unsigned long) grpc_google_default_credentials_create);
-    printf("%lx", (unsigned long) grpc_set_ssl_roots_override_callback);
-    printf("%lx", (unsigned long) grpc_ssl_credentials_create);
-    printf("%lx", (unsigned long) grpc_call_credentials_release);
-    printf("%lx", (unsigned long) grpc_composite_channel_credentials_create);
-    printf("%lx", (unsigned long) grpc_composite_call_credentials_create);
-    printf("%lx", (unsigned long) grpc_google_compute_engine_credentials_create);
-    printf("%lx", (unsigned long) grpc_max_auth_token_lifetime);
-    printf("%lx", (unsigned long) grpc_service_account_jwt_access_credentials_create);
-    printf("%lx", (unsigned long) grpc_google_refresh_token_credentials_create);
-    printf("%lx", (unsigned long) grpc_access_token_credentials_create);
-    printf("%lx", (unsigned long) grpc_google_iam_credentials_create);
-    printf("%lx", (unsigned long) grpc_metadata_credentials_create_from_plugin);
-    printf("%lx", (unsigned long) grpc_secure_channel_create);
-    printf("%lx", (unsigned long) grpc_server_credentials_release);
-    printf("%lx", (unsigned long) grpc_ssl_server_certificate_config_create);
-    printf("%lx", (unsigned long) grpc_ssl_server_certificate_config_destroy);
-    printf("%lx", (unsigned long) grpc_ssl_server_credentials_create);
-    printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_ex);
-    printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_options_using_config);
-    printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_options_using_config_fetcher);
-    printf("%lx", (unsigned long) grpc_ssl_server_credentials_options_destroy);
-    printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_with_options);
-    printf("%lx", (unsigned long) grpc_server_add_secure_http2_port);
-    printf("%lx", (unsigned long) grpc_call_set_credentials);
-    printf("%lx", (unsigned long) grpc_server_credentials_set_auth_metadata_processor);
-    printf("%lx", (unsigned long) grpc_raw_byte_buffer_create);
-    printf("%lx", (unsigned long) grpc_raw_compressed_byte_buffer_create);
-    printf("%lx", (unsigned long) grpc_byte_buffer_copy);
-    printf("%lx", (unsigned long) grpc_byte_buffer_length);
-    printf("%lx", (unsigned long) grpc_byte_buffer_destroy);
-    printf("%lx", (unsigned long) grpc_byte_buffer_reader_init);
-    printf("%lx", (unsigned long) grpc_byte_buffer_reader_destroy);
-    printf("%lx", (unsigned long) grpc_byte_buffer_reader_next);
-    printf("%lx", (unsigned long) grpc_byte_buffer_reader_readall);
-    printf("%lx", (unsigned long) grpc_raw_byte_buffer_from_reader);
-    printf("%lx", (unsigned long) grpc_slice_ref);
-    printf("%lx", (unsigned long) grpc_slice_unref);
-    printf("%lx", (unsigned long) grpc_slice_copy);
-    printf("%lx", (unsigned long) grpc_slice_new);
-    printf("%lx", (unsigned long) grpc_slice_new_with_user_data);
-    printf("%lx", (unsigned long) grpc_slice_new_with_len);
-    printf("%lx", (unsigned long) grpc_slice_malloc);
-    printf("%lx", (unsigned long) grpc_slice_malloc_large);
-    printf("%lx", (unsigned long) grpc_slice_intern);
-    printf("%lx", (unsigned long) grpc_slice_from_copied_string);
-    printf("%lx", (unsigned long) grpc_slice_from_copied_buffer);
-    printf("%lx", (unsigned long) grpc_slice_from_static_string);
-    printf("%lx", (unsigned long) grpc_slice_from_static_buffer);
-    printf("%lx", (unsigned long) grpc_slice_sub);
-    printf("%lx", (unsigned long) grpc_slice_sub_no_ref);
-    printf("%lx", (unsigned long) grpc_slice_split_tail);
-    printf("%lx", (unsigned long) grpc_slice_split_tail_maybe_ref);
-    printf("%lx", (unsigned long) grpc_slice_split_head);
-    printf("%lx", (unsigned long) grpc_empty_slice);
-    printf("%lx", (unsigned long) grpc_slice_default_hash_impl);
-    printf("%lx", (unsigned long) grpc_slice_default_eq_impl);
-    printf("%lx", (unsigned long) grpc_slice_eq);
-    printf("%lx", (unsigned long) grpc_slice_cmp);
-    printf("%lx", (unsigned long) grpc_slice_str_cmp);
-    printf("%lx", (unsigned long) grpc_slice_buf_start_eq);
-    printf("%lx", (unsigned long) grpc_slice_rchr);
-    printf("%lx", (unsigned long) grpc_slice_chr);
-    printf("%lx", (unsigned long) grpc_slice_slice);
-    printf("%lx", (unsigned long) grpc_slice_hash);
-    printf("%lx", (unsigned long) grpc_slice_is_equivalent);
-    printf("%lx", (unsigned long) grpc_slice_dup);
-    printf("%lx", (unsigned long) grpc_slice_to_c_string);
-    printf("%lx", (unsigned long) grpc_slice_buffer_init);
-    printf("%lx", (unsigned long) grpc_slice_buffer_destroy);
-    printf("%lx", (unsigned long) grpc_slice_buffer_add);
-    printf("%lx", (unsigned long) grpc_slice_buffer_add_indexed);
-    printf("%lx", (unsigned long) grpc_slice_buffer_addn);
-    printf("%lx", (unsigned long) grpc_slice_buffer_tiny_add);
-    printf("%lx", (unsigned long) grpc_slice_buffer_pop);
-    printf("%lx", (unsigned long) grpc_slice_buffer_reset_and_unref);
-    printf("%lx", (unsigned long) grpc_slice_buffer_swap);
-    printf("%lx", (unsigned long) grpc_slice_buffer_move_into);
-    printf("%lx", (unsigned long) grpc_slice_buffer_trim_end);
-    printf("%lx", (unsigned long) grpc_slice_buffer_move_first);
-    printf("%lx", (unsigned long) grpc_slice_buffer_move_first_no_ref);
-    printf("%lx", (unsigned long) grpc_slice_buffer_move_first_into_buffer);
-    printf("%lx", (unsigned long) grpc_slice_buffer_take_first);
-    printf("%lx", (unsigned long) grpc_slice_buffer_undo_take_first);
-    printf("%lx", (unsigned long) gpr_malloc);
-    printf("%lx", (unsigned long) gpr_zalloc);
-    printf("%lx", (unsigned long) gpr_free);
-    printf("%lx", (unsigned long) gpr_realloc);
-    printf("%lx", (unsigned long) gpr_malloc_aligned);
-    printf("%lx", (unsigned long) gpr_free_aligned);
-    printf("%lx", (unsigned long) gpr_set_allocation_functions);
-    printf("%lx", (unsigned long) gpr_get_allocation_functions);
-    printf("%lx", (unsigned long) gpr_avl_create);
-    printf("%lx", (unsigned long) gpr_avl_ref);
-    printf("%lx", (unsigned long) gpr_avl_unref);
-    printf("%lx", (unsigned long) gpr_avl_add);
-    printf("%lx", (unsigned long) gpr_avl_remove);
-    printf("%lx", (unsigned long) gpr_avl_get);
-    printf("%lx", (unsigned long) gpr_avl_maybe_get);
-    printf("%lx", (unsigned long) gpr_avl_is_empty);
-    printf("%lx", (unsigned long) gpr_cmdline_create);
-    printf("%lx", (unsigned long) gpr_cmdline_add_int);
-    printf("%lx", (unsigned long) gpr_cmdline_add_flag);
-    printf("%lx", (unsigned long) gpr_cmdline_add_string);
-    printf("%lx", (unsigned long) gpr_cmdline_on_extra_arg);
-    printf("%lx", (unsigned long) gpr_cmdline_set_survive_failure);
-    printf("%lx", (unsigned long) gpr_cmdline_parse);
-    printf("%lx", (unsigned long) gpr_cmdline_destroy);
-    printf("%lx", (unsigned long) gpr_cmdline_usage_string);
-    printf("%lx", (unsigned long) gpr_cpu_num_cores);
-    printf("%lx", (unsigned long) gpr_cpu_current_cpu);
-    printf("%lx", (unsigned long) gpr_histogram_create);
-    printf("%lx", (unsigned long) gpr_histogram_destroy);
-    printf("%lx", (unsigned long) gpr_histogram_add);
-    printf("%lx", (unsigned long) gpr_histogram_merge);
-    printf("%lx", (unsigned long) gpr_histogram_percentile);
-    printf("%lx", (unsigned long) gpr_histogram_mean);
-    printf("%lx", (unsigned long) gpr_histogram_stddev);
-    printf("%lx", (unsigned long) gpr_histogram_variance);
-    printf("%lx", (unsigned long) gpr_histogram_maximum);
-    printf("%lx", (unsigned long) gpr_histogram_minimum);
-    printf("%lx", (unsigned long) gpr_histogram_count);
-    printf("%lx", (unsigned long) gpr_histogram_sum);
-    printf("%lx", (unsigned long) gpr_histogram_sum_of_squares);
-    printf("%lx", (unsigned long) gpr_histogram_get_contents);
-    printf("%lx", (unsigned long) gpr_histogram_merge_contents);
-    printf("%lx", (unsigned long) gpr_join_host_port);
-    printf("%lx", (unsigned long) gpr_split_host_port);
-    printf("%lx", (unsigned long) gpr_log_severity_string);
-    printf("%lx", (unsigned long) gpr_log);
-    printf("%lx", (unsigned long) gpr_log_message);
-    printf("%lx", (unsigned long) gpr_set_log_verbosity);
-    printf("%lx", (unsigned long) gpr_log_verbosity_init);
-    printf("%lx", (unsigned long) gpr_set_log_function);
-    printf("%lx", (unsigned long) gpr_strdup);
-    printf("%lx", (unsigned long) gpr_asprintf);
-    printf("%lx", (unsigned long) gpr_subprocess_binary_extension);
-    printf("%lx", (unsigned long) gpr_subprocess_create);
-    printf("%lx", (unsigned long) gpr_subprocess_destroy);
-    printf("%lx", (unsigned long) gpr_subprocess_join);
-    printf("%lx", (unsigned long) gpr_subprocess_interrupt);
-    printf("%lx", (unsigned long) gpr_mu_init);
-    printf("%lx", (unsigned long) gpr_mu_destroy);
-    printf("%lx", (unsigned long) gpr_mu_lock);
-    printf("%lx", (unsigned long) gpr_mu_unlock);
-    printf("%lx", (unsigned long) gpr_mu_trylock);
-    printf("%lx", (unsigned long) gpr_cv_init);
-    printf("%lx", (unsigned long) gpr_cv_destroy);
-    printf("%lx", (unsigned long) gpr_cv_wait);
-    printf("%lx", (unsigned long) gpr_cv_signal);
-    printf("%lx", (unsigned long) gpr_cv_broadcast);
-    printf("%lx", (unsigned long) gpr_once_init);
-    printf("%lx", (unsigned long) gpr_event_init);
-    printf("%lx", (unsigned long) gpr_event_set);
-    printf("%lx", (unsigned long) gpr_event_get);
-    printf("%lx", (unsigned long) gpr_event_wait);
-    printf("%lx", (unsigned long) gpr_ref_init);
-    printf("%lx", (unsigned long) gpr_ref);
-    printf("%lx", (unsigned long) gpr_ref_non_zero);
-    printf("%lx", (unsigned long) gpr_refn);
-    printf("%lx", (unsigned long) gpr_unref);
-    printf("%lx", (unsigned long) gpr_ref_is_unique);
-    printf("%lx", (unsigned long) gpr_stats_init);
-    printf("%lx", (unsigned long) gpr_stats_inc);
-    printf("%lx", (unsigned long) gpr_stats_read);
-    printf("%lx", (unsigned long) gpr_thd_new);
-    printf("%lx", (unsigned long) gpr_thd_options_default);
-    printf("%lx", (unsigned long) gpr_thd_options_set_detached);
-    printf("%lx", (unsigned long) gpr_thd_options_set_joinable);
-    printf("%lx", (unsigned long) gpr_thd_options_is_detached);
-    printf("%lx", (unsigned long) gpr_thd_options_is_joinable);
-    printf("%lx", (unsigned long) gpr_thd_currentid);
-    printf("%lx", (unsigned long) gpr_thd_join);
-    printf("%lx", (unsigned long) gpr_time_0);
-    printf("%lx", (unsigned long) gpr_inf_future);
-    printf("%lx", (unsigned long) gpr_inf_past);
-    printf("%lx", (unsigned long) gpr_time_init);
-    printf("%lx", (unsigned long) gpr_now);
-    printf("%lx", (unsigned long) gpr_convert_clock_type);
-    printf("%lx", (unsigned long) gpr_time_cmp);
-    printf("%lx", (unsigned long) gpr_time_max);
-    printf("%lx", (unsigned long) gpr_time_min);
-    printf("%lx", (unsigned long) gpr_time_add);
-    printf("%lx", (unsigned long) gpr_time_sub);
-    printf("%lx", (unsigned long) gpr_time_from_micros);
-    printf("%lx", (unsigned long) gpr_time_from_nanos);
-    printf("%lx", (unsigned long) gpr_time_from_millis);
-    printf("%lx", (unsigned long) gpr_time_from_seconds);
-    printf("%lx", (unsigned long) gpr_time_from_minutes);
-    printf("%lx", (unsigned long) gpr_time_from_hours);
-    printf("%lx", (unsigned long) gpr_time_to_millis);
-    printf("%lx", (unsigned long) gpr_time_similar);
-    printf("%lx", (unsigned long) gpr_sleep_until);
-    printf("%lx", (unsigned long) gpr_timespec_to_micros);
-  }
+  printf("%lx", (unsigned long) grpc_compression_algorithm_parse);
+  printf("%lx", (unsigned long) grpc_compression_algorithm_name);
+  printf("%lx", (unsigned long) grpc_stream_compression_algorithm_name);
+  printf("%lx", (unsigned long) grpc_compression_algorithm_for_level);
+  printf("%lx", (unsigned long) grpc_stream_compression_algorithm_for_level);
+  printf("%lx", (unsigned long) grpc_compression_options_init);
+  printf("%lx", (unsigned long) grpc_compression_options_enable_algorithm);
+  printf("%lx", (unsigned long) grpc_compression_options_disable_algorithm);
+  printf("%lx", (unsigned long) grpc_compression_options_is_algorithm_enabled);
+  printf("%lx", (unsigned long) grpc_compression_options_is_stream_compression_algorithm_enabled);
+  printf("%lx", (unsigned long) grpc_metadata_array_init);
+  printf("%lx", (unsigned long) grpc_metadata_array_destroy);
+  printf("%lx", (unsigned long) grpc_call_details_init);
+  printf("%lx", (unsigned long) grpc_call_details_destroy);
+  printf("%lx", (unsigned long) grpc_register_plugin);
+  printf("%lx", (unsigned long) grpc_init);
+  printf("%lx", (unsigned long) grpc_shutdown);
+  printf("%lx", (unsigned long) grpc_version_string);
+  printf("%lx", (unsigned long) grpc_g_stands_for);
+  printf("%lx", (unsigned long) grpc_completion_queue_factory_lookup);
+  printf("%lx", (unsigned long) grpc_completion_queue_create_for_next);
+  printf("%lx", (unsigned long) grpc_completion_queue_create_for_pluck);
+  printf("%lx", (unsigned long) grpc_completion_queue_create);
+  printf("%lx", (unsigned long) grpc_completion_queue_next);
+  printf("%lx", (unsigned long) grpc_completion_queue_pluck);
+  printf("%lx", (unsigned long) grpc_completion_queue_shutdown);
+  printf("%lx", (unsigned long) grpc_completion_queue_destroy);
+  printf("%lx", (unsigned long) grpc_completion_queue_thread_local_cache_init);
+  printf("%lx", (unsigned long) grpc_completion_queue_thread_local_cache_flush);
+  printf("%lx", (unsigned long) grpc_alarm_create);
+  printf("%lx", (unsigned long) grpc_alarm_set);
+  printf("%lx", (unsigned long) grpc_alarm_cancel);
+  printf("%lx", (unsigned long) grpc_alarm_destroy);
+  printf("%lx", (unsigned long) grpc_channel_check_connectivity_state);
+  printf("%lx", (unsigned long) grpc_channel_num_external_connectivity_watchers);
+  printf("%lx", (unsigned long) grpc_channel_watch_connectivity_state);
+  printf("%lx", (unsigned long) grpc_channel_support_connectivity_watcher);
+  printf("%lx", (unsigned long) grpc_channel_create_call);
+  printf("%lx", (unsigned long) grpc_channel_ping);
+  printf("%lx", (unsigned long) grpc_channel_register_call);
+  printf("%lx", (unsigned long) grpc_channel_create_registered_call);
+  printf("%lx", (unsigned long) grpc_call_arena_alloc);
+  printf("%lx", (unsigned long) grpc_call_start_batch);
+  printf("%lx", (unsigned long) grpc_call_get_peer);
+  printf("%lx", (unsigned long) grpc_census_call_set_context);
+  printf("%lx", (unsigned long) grpc_census_call_get_context);
+  printf("%lx", (unsigned long) grpc_channel_get_target);
+  printf("%lx", (unsigned long) grpc_channel_get_info);
+  printf("%lx", (unsigned long) grpc_insecure_channel_create);
+  printf("%lx", (unsigned long) grpc_lame_client_channel_create);
+  printf("%lx", (unsigned long) grpc_channel_destroy);
+  printf("%lx", (unsigned long) grpc_call_cancel);
+  printf("%lx", (unsigned long) grpc_call_cancel_with_status);
+  printf("%lx", (unsigned long) grpc_call_ref);
+  printf("%lx", (unsigned long) grpc_call_unref);
+  printf("%lx", (unsigned long) grpc_server_request_call);
+  printf("%lx", (unsigned long) grpc_server_register_method);
+  printf("%lx", (unsigned long) grpc_server_request_registered_call);
+  printf("%lx", (unsigned long) grpc_server_create);
+  printf("%lx", (unsigned long) grpc_server_register_completion_queue);
+  printf("%lx", (unsigned long) grpc_server_add_insecure_http2_port);
+  printf("%lx", (unsigned long) grpc_server_start);
+  printf("%lx", (unsigned long) grpc_server_shutdown_and_notify);
+  printf("%lx", (unsigned long) grpc_server_cancel_all_calls);
+  printf("%lx", (unsigned long) grpc_server_destroy);
+  printf("%lx", (unsigned long) grpc_tracer_set_enabled);
+  printf("%lx", (unsigned long) grpc_header_key_is_legal);
+  printf("%lx", (unsigned long) grpc_header_nonbin_value_is_legal);
+  printf("%lx", (unsigned long) grpc_is_binary_header);
+  printf("%lx", (unsigned long) grpc_call_error_to_string);
+  printf("%lx", (unsigned long) grpc_resource_quota_create);
+  printf("%lx", (unsigned long) grpc_resource_quota_ref);
+  printf("%lx", (unsigned long) grpc_resource_quota_unref);
+  printf("%lx", (unsigned long) grpc_resource_quota_resize);
+  printf("%lx", (unsigned long) grpc_resource_quota_arg_vtable);
+  printf("%lx", (unsigned long) grpc_auth_property_iterator_next);
+  printf("%lx", (unsigned long) grpc_auth_context_property_iterator);
+  printf("%lx", (unsigned long) grpc_auth_context_peer_identity);
+  printf("%lx", (unsigned long) grpc_auth_context_find_properties_by_name);
+  printf("%lx", (unsigned long) grpc_auth_context_peer_identity_property_name);
+  printf("%lx", (unsigned long) grpc_auth_context_peer_is_authenticated);
+  printf("%lx", (unsigned long) grpc_call_auth_context);
+  printf("%lx", (unsigned long) grpc_auth_context_release);
+  printf("%lx", (unsigned long) grpc_auth_context_add_property);
+  printf("%lx", (unsigned long) grpc_auth_context_add_cstring_property);
+  printf("%lx", (unsigned long) grpc_auth_context_set_peer_identity_property_name);
+  printf("%lx", (unsigned long) grpc_channel_credentials_release);
+  printf("%lx", (unsigned long) grpc_google_default_credentials_create);
+  printf("%lx", (unsigned long) grpc_set_ssl_roots_override_callback);
+  printf("%lx", (unsigned long) grpc_ssl_credentials_create);
+  printf("%lx", (unsigned long) grpc_call_credentials_release);
+  printf("%lx", (unsigned long) grpc_composite_channel_credentials_create);
+  printf("%lx", (unsigned long) grpc_composite_call_credentials_create);
+  printf("%lx", (unsigned long) grpc_google_compute_engine_credentials_create);
+  printf("%lx", (unsigned long) grpc_max_auth_token_lifetime);
+  printf("%lx", (unsigned long) grpc_service_account_jwt_access_credentials_create);
+  printf("%lx", (unsigned long) grpc_google_refresh_token_credentials_create);
+  printf("%lx", (unsigned long) grpc_access_token_credentials_create);
+  printf("%lx", (unsigned long) grpc_google_iam_credentials_create);
+  printf("%lx", (unsigned long) grpc_metadata_credentials_create_from_plugin);
+  printf("%lx", (unsigned long) grpc_secure_channel_create);
+  printf("%lx", (unsigned long) grpc_server_credentials_release);
+  printf("%lx", (unsigned long) grpc_ssl_server_certificate_config_create);
+  printf("%lx", (unsigned long) grpc_ssl_server_certificate_config_destroy);
+  printf("%lx", (unsigned long) grpc_ssl_server_credentials_create);
+  printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_ex);
+  printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_options_using_config);
+  printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_options_using_config_fetcher);
+  printf("%lx", (unsigned long) grpc_ssl_server_credentials_options_destroy);
+  printf("%lx", (unsigned long) grpc_ssl_server_credentials_create_with_options);
+  printf("%lx", (unsigned long) grpc_server_add_secure_http2_port);
+  printf("%lx", (unsigned long) grpc_call_set_credentials);
+  printf("%lx", (unsigned long) grpc_server_credentials_set_auth_metadata_processor);
+  printf("%lx", (unsigned long) grpc_raw_byte_buffer_create);
+  printf("%lx", (unsigned long) grpc_raw_compressed_byte_buffer_create);
+  printf("%lx", (unsigned long) grpc_byte_buffer_copy);
+  printf("%lx", (unsigned long) grpc_byte_buffer_length);
+  printf("%lx", (unsigned long) grpc_byte_buffer_destroy);
+  printf("%lx", (unsigned long) grpc_byte_buffer_reader_init);
+  printf("%lx", (unsigned long) grpc_byte_buffer_reader_destroy);
+  printf("%lx", (unsigned long) grpc_byte_buffer_reader_next);
+  printf("%lx", (unsigned long) grpc_byte_buffer_reader_readall);
+  printf("%lx", (unsigned long) grpc_raw_byte_buffer_from_reader);
+  printf("%lx", (unsigned long) grpc_slice_ref);
+  printf("%lx", (unsigned long) grpc_slice_unref);
+  printf("%lx", (unsigned long) grpc_slice_copy);
+  printf("%lx", (unsigned long) grpc_slice_new);
+  printf("%lx", (unsigned long) grpc_slice_new_with_user_data);
+  printf("%lx", (unsigned long) grpc_slice_new_with_len);
+  printf("%lx", (unsigned long) grpc_slice_malloc);
+  printf("%lx", (unsigned long) grpc_slice_malloc_large);
+  printf("%lx", (unsigned long) grpc_slice_intern);
+  printf("%lx", (unsigned long) grpc_slice_from_copied_string);
+  printf("%lx", (unsigned long) grpc_slice_from_copied_buffer);
+  printf("%lx", (unsigned long) grpc_slice_from_static_string);
+  printf("%lx", (unsigned long) grpc_slice_from_static_buffer);
+  printf("%lx", (unsigned long) grpc_slice_sub);
+  printf("%lx", (unsigned long) grpc_slice_sub_no_ref);
+  printf("%lx", (unsigned long) grpc_slice_split_tail);
+  printf("%lx", (unsigned long) grpc_slice_split_tail_maybe_ref);
+  printf("%lx", (unsigned long) grpc_slice_split_head);
+  printf("%lx", (unsigned long) grpc_empty_slice);
+  printf("%lx", (unsigned long) grpc_slice_default_hash_impl);
+  printf("%lx", (unsigned long) grpc_slice_default_eq_impl);
+  printf("%lx", (unsigned long) grpc_slice_eq);
+  printf("%lx", (unsigned long) grpc_slice_cmp);
+  printf("%lx", (unsigned long) grpc_slice_str_cmp);
+  printf("%lx", (unsigned long) grpc_slice_buf_start_eq);
+  printf("%lx", (unsigned long) grpc_slice_rchr);
+  printf("%lx", (unsigned long) grpc_slice_chr);
+  printf("%lx", (unsigned long) grpc_slice_slice);
+  printf("%lx", (unsigned long) grpc_slice_hash);
+  printf("%lx", (unsigned long) grpc_slice_is_equivalent);
+  printf("%lx", (unsigned long) grpc_slice_dup);
+  printf("%lx", (unsigned long) grpc_slice_to_c_string);
+  printf("%lx", (unsigned long) grpc_slice_buffer_init);
+  printf("%lx", (unsigned long) grpc_slice_buffer_destroy);
+  printf("%lx", (unsigned long) grpc_slice_buffer_add);
+  printf("%lx", (unsigned long) grpc_slice_buffer_add_indexed);
+  printf("%lx", (unsigned long) grpc_slice_buffer_addn);
+  printf("%lx", (unsigned long) grpc_slice_buffer_tiny_add);
+  printf("%lx", (unsigned long) grpc_slice_buffer_pop);
+  printf("%lx", (unsigned long) grpc_slice_buffer_reset_and_unref);
+  printf("%lx", (unsigned long) grpc_slice_buffer_swap);
+  printf("%lx", (unsigned long) grpc_slice_buffer_move_into);
+  printf("%lx", (unsigned long) grpc_slice_buffer_trim_end);
+  printf("%lx", (unsigned long) grpc_slice_buffer_move_first);
+  printf("%lx", (unsigned long) grpc_slice_buffer_move_first_no_ref);
+  printf("%lx", (unsigned long) grpc_slice_buffer_move_first_into_buffer);
+  printf("%lx", (unsigned long) grpc_slice_buffer_take_first);
+  printf("%lx", (unsigned long) grpc_slice_buffer_undo_take_first);
+  printf("%lx", (unsigned long) gpr_malloc);
+  printf("%lx", (unsigned long) gpr_zalloc);
+  printf("%lx", (unsigned long) gpr_free);
+  printf("%lx", (unsigned long) gpr_realloc);
+  printf("%lx", (unsigned long) gpr_malloc_aligned);
+  printf("%lx", (unsigned long) gpr_free_aligned);
+  printf("%lx", (unsigned long) gpr_set_allocation_functions);
+  printf("%lx", (unsigned long) gpr_get_allocation_functions);
+  printf("%lx", (unsigned long) gpr_avl_create);
+  printf("%lx", (unsigned long) gpr_avl_ref);
+  printf("%lx", (unsigned long) gpr_avl_unref);
+  printf("%lx", (unsigned long) gpr_avl_add);
+  printf("%lx", (unsigned long) gpr_avl_remove);
+  printf("%lx", (unsigned long) gpr_avl_get);
+  printf("%lx", (unsigned long) gpr_avl_maybe_get);
+  printf("%lx", (unsigned long) gpr_avl_is_empty);
+  printf("%lx", (unsigned long) gpr_cmdline_create);
+  printf("%lx", (unsigned long) gpr_cmdline_add_int);
+  printf("%lx", (unsigned long) gpr_cmdline_add_flag);
+  printf("%lx", (unsigned long) gpr_cmdline_add_string);
+  printf("%lx", (unsigned long) gpr_cmdline_on_extra_arg);
+  printf("%lx", (unsigned long) gpr_cmdline_set_survive_failure);
+  printf("%lx", (unsigned long) gpr_cmdline_parse);
+  printf("%lx", (unsigned long) gpr_cmdline_destroy);
+  printf("%lx", (unsigned long) gpr_cmdline_usage_string);
+  printf("%lx", (unsigned long) gpr_cpu_num_cores);
+  printf("%lx", (unsigned long) gpr_cpu_current_cpu);
+  printf("%lx", (unsigned long) gpr_histogram_create);
+  printf("%lx", (unsigned long) gpr_histogram_destroy);
+  printf("%lx", (unsigned long) gpr_histogram_add);
+  printf("%lx", (unsigned long) gpr_histogram_merge);
+  printf("%lx", (unsigned long) gpr_histogram_percentile);
+  printf("%lx", (unsigned long) gpr_histogram_mean);
+  printf("%lx", (unsigned long) gpr_histogram_stddev);
+  printf("%lx", (unsigned long) gpr_histogram_variance);
+  printf("%lx", (unsigned long) gpr_histogram_maximum);
+  printf("%lx", (unsigned long) gpr_histogram_minimum);
+  printf("%lx", (unsigned long) gpr_histogram_count);
+  printf("%lx", (unsigned long) gpr_histogram_sum);
+  printf("%lx", (unsigned long) gpr_histogram_sum_of_squares);
+  printf("%lx", (unsigned long) gpr_histogram_get_contents);
+  printf("%lx", (unsigned long) gpr_histogram_merge_contents);
+  printf("%lx", (unsigned long) gpr_join_host_port);
+  printf("%lx", (unsigned long) gpr_split_host_port);
+  printf("%lx", (unsigned long) gpr_log_severity_string);
+  printf("%lx", (unsigned long) gpr_log);
+  printf("%lx", (unsigned long) gpr_log_message);
+  printf("%lx", (unsigned long) gpr_set_log_verbosity);
+  printf("%lx", (unsigned long) gpr_log_verbosity_init);
+  printf("%lx", (unsigned long) gpr_set_log_function);
+  printf("%lx", (unsigned long) gpr_strdup);
+  printf("%lx", (unsigned long) gpr_asprintf);
+  printf("%lx", (unsigned long) gpr_subprocess_binary_extension);
+  printf("%lx", (unsigned long) gpr_subprocess_create);
+  printf("%lx", (unsigned long) gpr_subprocess_destroy);
+  printf("%lx", (unsigned long) gpr_subprocess_join);
+  printf("%lx", (unsigned long) gpr_subprocess_interrupt);
+  printf("%lx", (unsigned long) gpr_mu_init);
+  printf("%lx", (unsigned long) gpr_mu_destroy);
+  printf("%lx", (unsigned long) gpr_mu_lock);
+  printf("%lx", (unsigned long) gpr_mu_unlock);
+  printf("%lx", (unsigned long) gpr_mu_trylock);
+  printf("%lx", (unsigned long) gpr_cv_init);
+  printf("%lx", (unsigned long) gpr_cv_destroy);
+  printf("%lx", (unsigned long) gpr_cv_wait);
+  printf("%lx", (unsigned long) gpr_cv_signal);
+  printf("%lx", (unsigned long) gpr_cv_broadcast);
+  printf("%lx", (unsigned long) gpr_once_init);
+  printf("%lx", (unsigned long) gpr_event_init);
+  printf("%lx", (unsigned long) gpr_event_set);
+  printf("%lx", (unsigned long) gpr_event_get);
+  printf("%lx", (unsigned long) gpr_event_wait);
+  printf("%lx", (unsigned long) gpr_ref_init);
+  printf("%lx", (unsigned long) gpr_ref);
+  printf("%lx", (unsigned long) gpr_ref_non_zero);
+  printf("%lx", (unsigned long) gpr_refn);
+  printf("%lx", (unsigned long) gpr_unref);
+  printf("%lx", (unsigned long) gpr_ref_is_unique);
+  printf("%lx", (unsigned long) gpr_stats_init);
+  printf("%lx", (unsigned long) gpr_stats_inc);
+  printf("%lx", (unsigned long) gpr_stats_read);
+  printf("%lx", (unsigned long) gpr_thd_new);
+  printf("%lx", (unsigned long) gpr_thd_options_default);
+  printf("%lx", (unsigned long) gpr_thd_options_set_detached);
+  printf("%lx", (unsigned long) gpr_thd_options_set_joinable);
+  printf("%lx", (unsigned long) gpr_thd_options_is_detached);
+  printf("%lx", (unsigned long) gpr_thd_options_is_joinable);
+  printf("%lx", (unsigned long) gpr_thd_currentid);
+  printf("%lx", (unsigned long) gpr_thd_join);
+  printf("%lx", (unsigned long) gpr_time_0);
+  printf("%lx", (unsigned long) gpr_inf_future);
+  printf("%lx", (unsigned long) gpr_inf_past);
+  printf("%lx", (unsigned long) gpr_time_init);
+  printf("%lx", (unsigned long) gpr_now);
+  printf("%lx", (unsigned long) gpr_convert_clock_type);
+  printf("%lx", (unsigned long) gpr_time_cmp);
+  printf("%lx", (unsigned long) gpr_time_max);
+  printf("%lx", (unsigned long) gpr_time_min);
+  printf("%lx", (unsigned long) gpr_time_add);
+  printf("%lx", (unsigned long) gpr_time_sub);
+  printf("%lx", (unsigned long) gpr_time_from_micros);
+  printf("%lx", (unsigned long) gpr_time_from_nanos);
+  printf("%lx", (unsigned long) gpr_time_from_millis);
+  printf("%lx", (unsigned long) gpr_time_from_seconds);
+  printf("%lx", (unsigned long) gpr_time_from_minutes);
+  printf("%lx", (unsigned long) gpr_time_from_hours);
+  printf("%lx", (unsigned long) gpr_time_to_millis);
+  printf("%lx", (unsigned long) gpr_time_similar);
+  printf("%lx", (unsigned long) gpr_sleep_until);
+  printf("%lx", (unsigned long) gpr_timespec_to_micros);
   return 0;
 }
diff --git a/test/cpp/microbenchmarks/bm_error.cc b/test/cpp/microbenchmarks/bm_error.cc
index 02a93e9..bbd8b3c 100644
--- a/test/cpp/microbenchmarks/bm_error.cc
+++ b/test/cpp/microbenchmarks/bm_error.cc
@@ -251,7 +251,7 @@
     grpc_status_code status;
     grpc_slice slice;
     grpc_error_get_status(&exec_ctx, fixture.error(), fixture.deadline(),
-                          &status, &slice, nullptr);
+                          &status, &slice, nullptr, nullptr);
   }
   grpc_exec_ctx_finish(&exec_ctx);
   track_counters.Finish(state);
@@ -265,7 +265,7 @@
   while (state.KeepRunning()) {
     grpc_status_code status;
     grpc_error_get_status(&exec_ctx, fixture.error(), fixture.deadline(),
-                          &status, nullptr, nullptr);
+                          &status, nullptr, nullptr, nullptr);
   }
   grpc_exec_ctx_finish(&exec_ctx);
   track_counters.Finish(state);
@@ -279,7 +279,7 @@
   while (state.KeepRunning()) {
     grpc_http2_error_code error;
     grpc_error_get_status(&exec_ctx, fixture.error(), fixture.deadline(),
-                          nullptr, nullptr, &error);
+                          nullptr, nullptr, &error, nullptr);
   }
   grpc_exec_ctx_finish(&exec_ctx);
   track_counters.Finish(state);
diff --git a/third_party/cares/cares.BUILD b/third_party/cares/cares.BUILD
index 8cc01b6..85ca506 100644
--- a/third_party/cares/cares.BUILD
+++ b/third_party/cares/cares.BUILD
@@ -8,7 +8,7 @@
 config_setting(
     name = "android",
     values = {
-      "crosstool_top": "//external:android/crosstool",
+        "crosstool_top": "//external:android/crosstool",
     },
 )
 
@@ -18,120 +18,128 @@
     name = "ios_x86_64",
     values = {"cpu": "ios_x86_64"},
 )
+
 config_setting(
     name = "ios_armv7",
     values = {"cpu": "ios_armv7"},
 )
+
 config_setting(
     name = "ios_armv7s",
     values = {"cpu": "ios_armv7s"},
 )
+
 config_setting(
     name = "ios_arm64",
     values = {"cpu": "ios_arm64"},
 )
 
+genrule(
+    name = "ares_build",
+    srcs = ["@cares_local_files//:ares_build_h"],
+    outs = ["ares_build.h"],
+    cmd = "cat $(location @cares_local_files//:ares_build_h) > $@",
+)
+
+# cc_library(
+#     name = "ares_build_h",
+#     hdrs = ["ares_build.h"],
+#     data = [":ares_build"],
+#     includes = ["."],
+# )
+
+genrule(
+    name = "ares_config",
+    srcs = ["@cares_local_files//:ares_config_h"],
+    outs = ["ares_config.h"],
+    cmd = "cat $(location @cares_local_files//:ares_config_h) > $@",
+)
+
+# cc_library(
+#     name = "ares_config_h",
+#     hdrs = ["ares_config.h"],
+#     data = [":ares_config"],
+#     includes = ["."],
+# )
+
 cc_library(
     name = "ares",
     srcs = [
-        "cares/ares__close_sockets.c",
-        "cares/ares__get_hostent.c",
-        "cares/ares__read_line.c",
-        "cares/ares__timeval.c",
-        "cares/ares_cancel.c",
-        "cares/ares_create_query.c",
-        "cares/ares_data.c",
-        "cares/ares_destroy.c",
-        "cares/ares_expand_name.c",
-        "cares/ares_expand_string.c",
-        "cares/ares_fds.c",
-        "cares/ares_free_hostent.c",
-        "cares/ares_free_string.c",
-        "cares/ares_getenv.c",
-        "cares/ares_gethostbyaddr.c",
-        "cares/ares_gethostbyname.c",
-        "cares/ares_getnameinfo.c",
-        "cares/ares_getopt.c",
-        "cares/ares_getsock.c",
-        "cares/ares_init.c",
-        "cares/ares_library_init.c",
-        "cares/ares_llist.c",
-        "cares/ares_mkquery.c",
-        "cares/ares_nowarn.c",
-        "cares/ares_options.c",
-        "cares/ares_parse_a_reply.c",
-        "cares/ares_parse_aaaa_reply.c",
-        "cares/ares_parse_mx_reply.c",
-        "cares/ares_parse_naptr_reply.c",
-        "cares/ares_parse_ns_reply.c",
-        "cares/ares_parse_ptr_reply.c",
-        "cares/ares_parse_soa_reply.c",
-        "cares/ares_parse_srv_reply.c",
-        "cares/ares_parse_txt_reply.c",
-        "cares/ares_platform.c",
-        "cares/ares_process.c",
-        "cares/ares_query.c",
-        "cares/ares_search.c",
-        "cares/ares_send.c",
-        "cares/ares_strcasecmp.c",
-        "cares/ares_strdup.c",
-        "cares/ares_strerror.c",
-        "cares/ares_timeout.c",
-        "cares/ares_version.c",
-        "cares/ares_writev.c",
-        "cares/bitncmp.c",
-        "cares/inet_net_pton.c",
-        "cares/inet_ntop.c",
-        "cares/windows_port.c",
+        "ares__close_sockets.c",
+        "ares__get_hostent.c",
+        "ares__read_line.c",
+        "ares__timeval.c",
+        "ares_cancel.c",
+        "ares_create_query.c",
+        "ares_data.c",
+        "ares_destroy.c",
+        "ares_expand_name.c",
+        "ares_expand_string.c",
+        "ares_fds.c",
+        "ares_free_hostent.c",
+        "ares_free_string.c",
+        "ares_getenv.c",
+        "ares_gethostbyaddr.c",
+        "ares_gethostbyname.c",
+        "ares_getnameinfo.c",
+        "ares_getopt.c",
+        "ares_getsock.c",
+        "ares_init.c",
+        "ares_library_init.c",
+        "ares_llist.c",
+        "ares_mkquery.c",
+        "ares_nowarn.c",
+        "ares_options.c",
+        "ares_parse_a_reply.c",
+        "ares_parse_aaaa_reply.c",
+        "ares_parse_mx_reply.c",
+        "ares_parse_naptr_reply.c",
+        "ares_parse_ns_reply.c",
+        "ares_parse_ptr_reply.c",
+        "ares_parse_soa_reply.c",
+        "ares_parse_srv_reply.c",
+        "ares_parse_txt_reply.c",
+        "ares_platform.c",
+        "ares_process.c",
+        "ares_query.c",
+        "ares_search.c",
+        "ares_send.c",
+        "ares_strcasecmp.c",
+        "ares_strdup.c",
+        "ares_strerror.c",
+        "ares_timeout.c",
+        "ares_version.c",
+        "ares_writev.c",
+        "bitncmp.c",
+        "inet_net_pton.c",
+        "inet_ntop.c",
+        "windows_port.c",
     ],
     hdrs = [
+        "ares.h",
         "ares_build.h",
-        "cares/ares.h",
-        "cares/ares_data.h",
-        "cares/ares_dns.h",
-        "cares/ares_getenv.h",
-        "cares/ares_getopt.h",
-        "cares/ares_inet_net_pton.h",
-        "cares/ares_iphlpapi.h",
-        "cares/ares_ipv6.h",
-        "cares/ares_library_init.h",
-        "cares/ares_llist.h",
-        "cares/ares_nowarn.h",
-        "cares/ares_platform.h",
-        "cares/ares_private.h",
-        "cares/ares_rules.h",
-        "cares/ares_setup.h",
-        "cares/ares_strcasecmp.h",
-        "cares/ares_strdup.h",
-        "cares/ares_version.h",
-        "cares/bitncmp.h",
-        "cares/config-win32.h",
-        "cares/nameser.h",
-        "cares/setup_once.h",
-    ] + select({
-        ":ios_x86_64": ["config_darwin/ares_config.h"],
-        ":ios_armv7": ["config_darwin/ares_config.h"],
-        ":ios_armv7s": ["config_darwin/ares_config.h"],
-        ":ios_arm64": ["config_darwin/ares_config.h"],
-        ":darwin": ["config_darwin/ares_config.h"],
-        ":android": ["config_android/ares_config.h"],
-        "//conditions:default": ["config_linux/ares_config.h"],
-    }),
-    includes = [
-        ".",
-        "cares"
-    ] + select({
-        ":ios_x86_64": ["config_darwin"],
-        ":ios_armv7": ["config_darwin"],
-        ":ios_armv7s": ["config_darwin"],
-        ":ios_arm64": ["config_darwin"],
-        ":darwin": ["config_darwin"],
-        ":android": ["config_android"],
-        "//conditions:default": ["config_linux"],
-    }),
-    linkstatic = 1,
-    visibility = [
-        "//visibility:public",
+        "ares_config.h",
+        "ares_data.h",
+        "ares_dns.h",
+        "ares_getenv.h",
+        "ares_getopt.h",
+        "ares_inet_net_pton.h",
+        "ares_iphlpapi.h",
+        "ares_ipv6.h",
+        "ares_library_init.h",
+        "ares_llist.h",
+        "ares_nowarn.h",
+        "ares_platform.h",
+        "ares_private.h",
+        "ares_rules.h",
+        "ares_setup.h",
+        "ares_strcasecmp.h",
+        "ares_strdup.h",
+        "ares_version.h",
+        "bitncmp.h",
+        "config-win32.h",
+        "nameser.h",
+        "setup_once.h",
     ],
     copts = [
         "-D_GNU_SOURCE",
@@ -139,4 +147,13 @@
         "-DNOMINMAX",
         "-DHAVE_CONFIG_H",
     ],
+    data = [
+        ":ares_build",
+        ":ares_config",
+    ],
+    includes = ["."],
+    linkstatic = 1,
+    visibility = [
+        "//visibility:public",
+    ],
 )
diff --git a/third_party/cares/cares_local_files.BUILD b/third_party/cares/cares_local_files.BUILD
new file mode 100644
index 0000000..fe59447
--- /dev/null
+++ b/third_party/cares/cares_local_files.BUILD
@@ -0,0 +1,57 @@
+package(
+    default_visibility = ["//visibility:public"],
+)
+
+config_setting(
+    name = "darwin",
+    values = {"cpu": "darwin"},
+)
+
+# Android is not officially supported through C++.
+# This just helps with the build for now.
+config_setting(
+    name = "android",
+    values = {
+        "crosstool_top": "//external:android/crosstool",
+    },
+)
+
+# iOS is not officially supported through C++.
+# This just helps with the build for now.
+config_setting(
+    name = "ios_x86_64",
+    values = {"cpu": "ios_x86_64"},
+)
+
+config_setting(
+    name = "ios_armv7",
+    values = {"cpu": "ios_armv7"},
+)
+
+config_setting(
+    name = "ios_armv7s",
+    values = {"cpu": "ios_armv7s"},
+)
+
+config_setting(
+    name = "ios_arm64",
+    values = {"cpu": "ios_arm64"},
+)
+
+filegroup(
+    name = "ares_build_h",
+    srcs = ["ares_build.h"],
+)
+
+filegroup(
+    name = "ares_config_h",
+    srcs = select({
+        ":ios_x86_64": ["config_darwin/ares_config.h"],
+        ":ios_armv7": ["config_darwin/ares_config.h"],
+        ":ios_armv7s": ["config_darwin/ares_config.h"],
+        ":ios_arm64": ["config_darwin/ares_config.h"],
+        ":darwin": ["config_darwin/ares_config.h"],
+        ":android": ["config_android/ares_config.h"],
+        "//conditions:default": ["config_linux/ares_config.h"],
+    }),
+)
diff --git a/third_party/protobuf b/third_party/protobuf
index 80a37e0..2761122 160000
--- a/third_party/protobuf
+++ b/third_party/protobuf
@@ -1 +1 @@
-Subproject commit 80a37e0782d2d702d52234b62dd4b9ec74fd2c95
+Subproject commit 2761122b810fe8861004ae785cc3ab39f384d342
diff --git a/tools/codegen/core/gen_nano_proto.sh b/tools/codegen/core/gen_nano_proto.sh
index db00bd9..4246840 100755
--- a/tools/codegen/core/gen_nano_proto.sh
+++ b/tools/codegen/core/gen_nano_proto.sh
@@ -68,7 +68,7 @@
 
 # this should be the same version as the submodule we compile against
 # ideally we'd update this as a template to ensure that
-pip install protobuf==3.2.0
+pip install protobuf==3.5.0.post1
 
 pushd "$(dirname $INPUT_PROTO)" > /dev/null
 
diff --git a/tools/distrib/python/check_grpcio_tools.py b/tools/distrib/python/check_grpcio_tools.py
index c5afa71..b56ccae 100755
--- a/tools/distrib/python/check_grpcio_tools.py
+++ b/tools/distrib/python/check_grpcio_tools.py
@@ -14,17 +14,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import cStringIO
-
-import make_grpcio_tools as make
+import make_grpcio_tools as _make
 
 OUT_OF_DATE_MESSAGE = """file {} is out of date
 
 Have you called tools/distrib/python/make_grpcio_tools.py since upgrading protobuf?"""
 
-check_protoc_lib_deps_content = make.get_deps()
+submodule_commit_hash = _make.protobuf_submodule_commit_hash()
 
-with open(make.GRPC_PYTHON_PROTOC_LIB_DEPS, 'r') as protoc_lib_deps_file:
-  if protoc_lib_deps_file.read() != check_protoc_lib_deps_content:
-    print(OUT_OF_DATE_MESSAGE.format(make.GRPC_PYTHON_PROTOC_LIB_DEPS))
-    raise SystemExit(1)
+with open(_make.GRPC_PYTHON_PROTOC_LIB_DEPS, 'r') as _protoc_lib_deps_file:
+  content = _protoc_lib_deps_file.read().splitlines()
+
+testString = (_make.COMMIT_HASH_PREFIX +
+              submodule_commit_hash +
+              _make.COMMIT_HASH_SUFFIX)
+
+if testString not in content:
+  print(OUT_OF_DATE_MESSAGE.format(_make.GRPC_PYTHON_PROTOC_LIB_DEPS))
+  raise SystemExit(1)
diff --git a/tools/distrib/python/grpcio_tools/protoc_lib_deps.py b/tools/distrib/python/grpcio_tools/protoc_lib_deps.py
index 18470d5..2c65fca 100644
--- a/tools/distrib/python/grpcio_tools/protoc_lib_deps.py
+++ b/tools/distrib/python/grpcio_tools/protoc_lib_deps.py
@@ -1,5 +1,5 @@
 
-# Copyright 2016 gRPC authors.
+# 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.
@@ -14,8 +14,10 @@
 # limitations under the License.
 
 # AUTO-GENERATED BY make_grpcio_tools.py!
-CC_FILES=['google/protobuf/compiler/zip_writer.cc', 'google/protobuf/compiler/subprocess.cc', 'google/protobuf/compiler/ruby/ruby_generator.cc', 'google/protobuf/compiler/python/python_generator.cc', 'google/protobuf/compiler/plugin.pb.cc', 'google/protobuf/compiler/plugin.cc', 'google/protobuf/compiler/php/php_generator.cc', 'google/protobuf/compiler/objectivec/objectivec_primitive_field.cc', 'google/protobuf/compiler/objectivec/objectivec_oneof.cc', 'google/protobuf/compiler/objectivec/objectivec_message_field.cc', 'google/protobuf/compiler/objectivec/objectivec_message.cc', 'google/protobuf/compiler/objectivec/objectivec_map_field.cc', 'google/protobuf/compiler/objectivec/objectivec_helpers.cc', 'google/protobuf/compiler/objectivec/objectivec_generator.cc', 'google/protobuf/compiler/objectivec/objectivec_file.cc', 'google/protobuf/compiler/objectivec/objectivec_field.cc', 'google/protobuf/compiler/objectivec/objectivec_extension.cc', 'google/protobuf/compiler/objectivec/objectivec_enum_field.cc', 'google/protobuf/compiler/objectivec/objectivec_enum.cc', 'google/protobuf/compiler/js/well_known_types_embed.cc', 'google/protobuf/compiler/js/js_generator.cc', 'google/protobuf/compiler/javanano/javanano_primitive_field.cc', 'google/protobuf/compiler/javanano/javanano_message_field.cc', 'google/protobuf/compiler/javanano/javanano_message.cc', 'google/protobuf/compiler/javanano/javanano_map_field.cc', 'google/protobuf/compiler/javanano/javanano_helpers.cc', 'google/protobuf/compiler/javanano/javanano_generator.cc', 'google/protobuf/compiler/javanano/javanano_file.cc', 'google/protobuf/compiler/javanano/javanano_field.cc', 'google/protobuf/compiler/javanano/javanano_extension.cc', 'google/protobuf/compiler/javanano/javanano_enum_field.cc', 'google/protobuf/compiler/javanano/javanano_enum.cc', 'google/protobuf/compiler/java/java_string_field_lite.cc', 'google/protobuf/compiler/java/java_string_field.cc', 'google/protobuf/compiler/java/java_shared_code_generator.cc', 'google/protobuf/compiler/java/java_service.cc', 'google/protobuf/compiler/java/java_primitive_field_lite.cc', 'google/protobuf/compiler/java/java_primitive_field.cc', 'google/protobuf/compiler/java/java_name_resolver.cc', 'google/protobuf/compiler/java/java_message_lite.cc', 'google/protobuf/compiler/java/java_message_field_lite.cc', 'google/protobuf/compiler/java/java_message_field.cc', 'google/protobuf/compiler/java/java_message_builder_lite.cc', 'google/protobuf/compiler/java/java_message_builder.cc', 'google/protobuf/compiler/java/java_message.cc', 'google/protobuf/compiler/java/java_map_field_lite.cc', 'google/protobuf/compiler/java/java_map_field.cc', 'google/protobuf/compiler/java/java_lazy_message_field_lite.cc', 'google/protobuf/compiler/java/java_lazy_message_field.cc', 'google/protobuf/compiler/java/java_helpers.cc', 'google/protobuf/compiler/java/java_generator_factory.cc', 'google/protobuf/compiler/java/java_generator.cc', 'google/protobuf/compiler/java/java_file.cc', 'google/protobuf/compiler/java/java_field.cc', 'google/protobuf/compiler/java/java_extension_lite.cc', 'google/protobuf/compiler/java/java_extension.cc', 'google/protobuf/compiler/java/java_enum_lite.cc', 'google/protobuf/compiler/java/java_enum_field_lite.cc', 'google/protobuf/compiler/java/java_enum_field.cc', 'google/protobuf/compiler/java/java_enum.cc', 'google/protobuf/compiler/java/java_doc_comment.cc', 'google/protobuf/compiler/java/java_context.cc', 'google/protobuf/compiler/csharp/csharp_wrapper_field.cc', 'google/protobuf/compiler/csharp/csharp_source_generator_base.cc', 'google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc', 'google/protobuf/compiler/csharp/csharp_repeated_message_field.cc', 'google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc', 'google/protobuf/compiler/csharp/csharp_reflection_class.cc', 'google/protobuf/compiler/csharp/csharp_primitive_field.cc', 'google/protobuf/compiler/csharp/csharp_message_field.cc', 'google/protobuf/compiler/csharp/csharp_message.cc', 'google/protobuf/compiler/csharp/csharp_map_field.cc', 'google/protobuf/compiler/csharp/csharp_helpers.cc', 'google/protobuf/compiler/csharp/csharp_generator.cc', 'google/protobuf/compiler/csharp/csharp_field_base.cc', 'google/protobuf/compiler/csharp/csharp_enum_field.cc', 'google/protobuf/compiler/csharp/csharp_enum.cc', 'google/protobuf/compiler/csharp/csharp_doc_comment.cc', 'google/protobuf/compiler/cpp/cpp_string_field.cc', 'google/protobuf/compiler/cpp/cpp_service.cc', 'google/protobuf/compiler/cpp/cpp_primitive_field.cc', 'google/protobuf/compiler/cpp/cpp_message_field.cc', 'google/protobuf/compiler/cpp/cpp_message.cc', 'google/protobuf/compiler/cpp/cpp_map_field.cc', 'google/protobuf/compiler/cpp/cpp_helpers.cc', 'google/protobuf/compiler/cpp/cpp_generator.cc', 'google/protobuf/compiler/cpp/cpp_file.cc', 'google/protobuf/compiler/cpp/cpp_field.cc', 'google/protobuf/compiler/cpp/cpp_extension.cc', 'google/protobuf/compiler/cpp/cpp_enum_field.cc', 'google/protobuf/compiler/cpp/cpp_enum.cc', 'google/protobuf/compiler/command_line_interface.cc', 'google/protobuf/compiler/code_generator.cc', 'google/protobuf/wrappers.pb.cc', 'google/protobuf/wire_format.cc', 'google/protobuf/util/type_resolver_util.cc', 'google/protobuf/util/time_util.cc', 'google/protobuf/util/message_differencer.cc', 'google/protobuf/util/json_util.cc', 'google/protobuf/util/internal/utility.cc', 'google/protobuf/util/internal/type_info_test_helper.cc', 'google/protobuf/util/internal/type_info.cc', 'google/protobuf/util/internal/protostream_objectwriter.cc', 'google/protobuf/util/internal/protostream_objectsource.cc', 'google/protobuf/util/internal/proto_writer.cc', 'google/protobuf/util/internal/object_writer.cc', 'google/protobuf/util/internal/json_stream_parser.cc', 'google/protobuf/util/internal/json_objectwriter.cc', 'google/protobuf/util/internal/json_escaping.cc', 'google/protobuf/util/internal/field_mask_utility.cc', 'google/protobuf/util/internal/error_listener.cc', 'google/protobuf/util/internal/default_value_objectwriter.cc', 'google/protobuf/util/internal/datapiece.cc', 'google/protobuf/util/field_mask_util.cc', 'google/protobuf/util/field_comparator.cc', 'google/protobuf/util/delimited_message_util.cc', 'google/protobuf/unknown_field_set.cc', 'google/protobuf/type.pb.cc', 'google/protobuf/timestamp.pb.cc', 'google/protobuf/text_format.cc', 'google/protobuf/stubs/substitute.cc', 'google/protobuf/stubs/mathlimits.cc', 'google/protobuf/struct.pb.cc', 'google/protobuf/source_context.pb.cc', 'google/protobuf/service.cc', 'google/protobuf/reflection_ops.cc', 'google/protobuf/message.cc', 'google/protobuf/map_field.cc', 'google/protobuf/io/zero_copy_stream_impl.cc', 'google/protobuf/io/tokenizer.cc', 'google/protobuf/io/strtod.cc', 'google/protobuf/io/printer.cc', 'google/protobuf/io/gzip_stream.cc', 'google/protobuf/generated_message_table_driven.cc', 'google/protobuf/generated_message_reflection.cc', 'google/protobuf/field_mask.pb.cc', 'google/protobuf/extension_set_heavy.cc', 'google/protobuf/empty.pb.cc', 'google/protobuf/dynamic_message.cc', 'google/protobuf/duration.pb.cc', 'google/protobuf/descriptor_database.cc', 'google/protobuf/descriptor.pb.cc', 'google/protobuf/descriptor.cc', 'google/protobuf/compiler/parser.cc', 'google/protobuf/compiler/importer.cc', 'google/protobuf/api.pb.cc', 'google/protobuf/any.pb.cc', 'google/protobuf/any.cc', 'google/protobuf/wire_format_lite.cc', 'google/protobuf/stubs/time.cc', 'google/protobuf/stubs/strutil.cc', 'google/protobuf/stubs/structurally_valid.cc', 'google/protobuf/stubs/stringprintf.cc', 'google/protobuf/stubs/stringpiece.cc', 'google/protobuf/stubs/statusor.cc', 'google/protobuf/stubs/status.cc', 'google/protobuf/stubs/once.cc', 'google/protobuf/stubs/io_win32.cc', 'google/protobuf/stubs/int128.cc', 'google/protobuf/stubs/common.cc', 'google/protobuf/stubs/bytestream.cc', 'google/protobuf/stubs/atomicops_internals_x86_msvc.cc', 'google/protobuf/stubs/atomicops_internals_x86_gcc.cc', 'google/protobuf/repeated_field.cc', 'google/protobuf/message_lite.cc', 'google/protobuf/io/zero_copy_stream_impl_lite.cc', 'google/protobuf/io/zero_copy_stream.cc', 'google/protobuf/io/coded_stream.cc', 'google/protobuf/generated_message_util.cc', 'google/protobuf/generated_message_table_driven_lite.cc', 'google/protobuf/extension_set.cc', 'google/protobuf/arenastring.cc', 'google/protobuf/arena.cc', 'google/protobuf/compiler/js/embed.cc']
+CC_FILES=['google/protobuf/compiler/zip_writer.cc', 'google/protobuf/compiler/subprocess.cc', 'google/protobuf/compiler/ruby/ruby_generator.cc', 'google/protobuf/compiler/python/python_generator.cc', 'google/protobuf/compiler/plugin.pb.cc', 'google/protobuf/compiler/plugin.cc', 'google/protobuf/compiler/php/php_generator.cc', 'google/protobuf/compiler/objectivec/objectivec_primitive_field.cc', 'google/protobuf/compiler/objectivec/objectivec_oneof.cc', 'google/protobuf/compiler/objectivec/objectivec_message_field.cc', 'google/protobuf/compiler/objectivec/objectivec_message.cc', 'google/protobuf/compiler/objectivec/objectivec_map_field.cc', 'google/protobuf/compiler/objectivec/objectivec_helpers.cc', 'google/protobuf/compiler/objectivec/objectivec_generator.cc', 'google/protobuf/compiler/objectivec/objectivec_file.cc', 'google/protobuf/compiler/objectivec/objectivec_field.cc', 'google/protobuf/compiler/objectivec/objectivec_extension.cc', 'google/protobuf/compiler/objectivec/objectivec_enum_field.cc', 'google/protobuf/compiler/objectivec/objectivec_enum.cc', 'google/protobuf/compiler/js/well_known_types_embed.cc', 'google/protobuf/compiler/js/js_generator.cc', 'google/protobuf/compiler/javanano/javanano_primitive_field.cc', 'google/protobuf/compiler/javanano/javanano_message_field.cc', 'google/protobuf/compiler/javanano/javanano_message.cc', 'google/protobuf/compiler/javanano/javanano_map_field.cc', 'google/protobuf/compiler/javanano/javanano_helpers.cc', 'google/protobuf/compiler/javanano/javanano_generator.cc', 'google/protobuf/compiler/javanano/javanano_file.cc', 'google/protobuf/compiler/javanano/javanano_field.cc', 'google/protobuf/compiler/javanano/javanano_extension.cc', 'google/protobuf/compiler/javanano/javanano_enum_field.cc', 'google/protobuf/compiler/javanano/javanano_enum.cc', 'google/protobuf/compiler/java/java_string_field_lite.cc', 'google/protobuf/compiler/java/java_string_field.cc', 'google/protobuf/compiler/java/java_shared_code_generator.cc', 'google/protobuf/compiler/java/java_service.cc', 'google/protobuf/compiler/java/java_primitive_field_lite.cc', 'google/protobuf/compiler/java/java_primitive_field.cc', 'google/protobuf/compiler/java/java_name_resolver.cc', 'google/protobuf/compiler/java/java_message_lite.cc', 'google/protobuf/compiler/java/java_message_field_lite.cc', 'google/protobuf/compiler/java/java_message_field.cc', 'google/protobuf/compiler/java/java_message_builder_lite.cc', 'google/protobuf/compiler/java/java_message_builder.cc', 'google/protobuf/compiler/java/java_message.cc', 'google/protobuf/compiler/java/java_map_field_lite.cc', 'google/protobuf/compiler/java/java_map_field.cc', 'google/protobuf/compiler/java/java_lazy_message_field_lite.cc', 'google/protobuf/compiler/java/java_lazy_message_field.cc', 'google/protobuf/compiler/java/java_helpers.cc', 'google/protobuf/compiler/java/java_generator_factory.cc', 'google/protobuf/compiler/java/java_generator.cc', 'google/protobuf/compiler/java/java_file.cc', 'google/protobuf/compiler/java/java_field.cc', 'google/protobuf/compiler/java/java_extension_lite.cc', 'google/protobuf/compiler/java/java_extension.cc', 'google/protobuf/compiler/java/java_enum_lite.cc', 'google/protobuf/compiler/java/java_enum_field_lite.cc', 'google/protobuf/compiler/java/java_enum_field.cc', 'google/protobuf/compiler/java/java_enum.cc', 'google/protobuf/compiler/java/java_doc_comment.cc', 'google/protobuf/compiler/java/java_context.cc', 'google/protobuf/compiler/csharp/csharp_wrapper_field.cc', 'google/protobuf/compiler/csharp/csharp_source_generator_base.cc', 'google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc', 'google/protobuf/compiler/csharp/csharp_repeated_message_field.cc', 'google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc', 'google/protobuf/compiler/csharp/csharp_reflection_class.cc', 'google/protobuf/compiler/csharp/csharp_primitive_field.cc', 'google/protobuf/compiler/csharp/csharp_message_field.cc', 'google/protobuf/compiler/csharp/csharp_message.cc', 'google/protobuf/compiler/csharp/csharp_map_field.cc', 'google/protobuf/compiler/csharp/csharp_helpers.cc', 'google/protobuf/compiler/csharp/csharp_generator.cc', 'google/protobuf/compiler/csharp/csharp_field_base.cc', 'google/protobuf/compiler/csharp/csharp_enum_field.cc', 'google/protobuf/compiler/csharp/csharp_enum.cc', 'google/protobuf/compiler/csharp/csharp_doc_comment.cc', 'google/protobuf/compiler/cpp/cpp_string_field.cc', 'google/protobuf/compiler/cpp/cpp_service.cc', 'google/protobuf/compiler/cpp/cpp_primitive_field.cc', 'google/protobuf/compiler/cpp/cpp_padding_optimizer.cc', 'google/protobuf/compiler/cpp/cpp_message_field.cc', 'google/protobuf/compiler/cpp/cpp_message.cc', 'google/protobuf/compiler/cpp/cpp_map_field.cc', 'google/protobuf/compiler/cpp/cpp_helpers.cc', 'google/protobuf/compiler/cpp/cpp_generator.cc', 'google/protobuf/compiler/cpp/cpp_file.cc', 'google/protobuf/compiler/cpp/cpp_field.cc', 'google/protobuf/compiler/cpp/cpp_extension.cc', 'google/protobuf/compiler/cpp/cpp_enum_field.cc', 'google/protobuf/compiler/cpp/cpp_enum.cc', 'google/protobuf/compiler/command_line_interface.cc', 'google/protobuf/compiler/code_generator.cc', 'google/protobuf/wrappers.pb.cc', 'google/protobuf/wire_format.cc', 'google/protobuf/util/type_resolver_util.cc', 'google/protobuf/util/time_util.cc', 'google/protobuf/util/message_differencer.cc', 'google/protobuf/util/json_util.cc', 'google/protobuf/util/internal/utility.cc', 'google/protobuf/util/internal/type_info_test_helper.cc', 'google/protobuf/util/internal/type_info.cc', 'google/protobuf/util/internal/protostream_objectwriter.cc', 'google/protobuf/util/internal/protostream_objectsource.cc', 'google/protobuf/util/internal/proto_writer.cc', 'google/protobuf/util/internal/object_writer.cc', 'google/protobuf/util/internal/json_stream_parser.cc', 'google/protobuf/util/internal/json_objectwriter.cc', 'google/protobuf/util/internal/json_escaping.cc', 'google/protobuf/util/internal/field_mask_utility.cc', 'google/protobuf/util/internal/error_listener.cc', 'google/protobuf/util/internal/default_value_objectwriter.cc', 'google/protobuf/util/internal/datapiece.cc', 'google/protobuf/util/field_mask_util.cc', 'google/protobuf/util/field_comparator.cc', 'google/protobuf/util/delimited_message_util.cc', 'google/protobuf/unknown_field_set.cc', 'google/protobuf/type.pb.cc', 'google/protobuf/timestamp.pb.cc', 'google/protobuf/text_format.cc', 'google/protobuf/stubs/substitute.cc', 'google/protobuf/stubs/mathlimits.cc', 'google/protobuf/struct.pb.cc', 'google/protobuf/source_context.pb.cc', 'google/protobuf/service.cc', 'google/protobuf/reflection_ops.cc', 'google/protobuf/message.cc', 'google/protobuf/map_field.cc', 'google/protobuf/io/zero_copy_stream_impl.cc', 'google/protobuf/io/tokenizer.cc', 'google/protobuf/io/strtod.cc', 'google/protobuf/io/printer.cc', 'google/protobuf/io/gzip_stream.cc', 'google/protobuf/generated_message_table_driven.cc', 'google/protobuf/generated_message_reflection.cc', 'google/protobuf/field_mask.pb.cc', 'google/protobuf/extension_set_heavy.cc', 'google/protobuf/empty.pb.cc', 'google/protobuf/dynamic_message.cc', 'google/protobuf/duration.pb.cc', 'google/protobuf/descriptor_database.cc', 'google/protobuf/descriptor.pb.cc', 'google/protobuf/descriptor.cc', 'google/protobuf/compiler/parser.cc', 'google/protobuf/compiler/importer.cc', 'google/protobuf/api.pb.cc', 'google/protobuf/any.pb.cc', 'google/protobuf/any.cc', 'google/protobuf/wire_format_lite.cc', 'google/protobuf/stubs/time.cc', 'google/protobuf/stubs/strutil.cc', 'google/protobuf/stubs/structurally_valid.cc', 'google/protobuf/stubs/stringprintf.cc', 'google/protobuf/stubs/stringpiece.cc', 'google/protobuf/stubs/statusor.cc', 'google/protobuf/stubs/status.cc', 'google/protobuf/stubs/once.cc', 'google/protobuf/stubs/io_win32.cc', 'google/protobuf/stubs/int128.cc', 'google/protobuf/stubs/common.cc', 'google/protobuf/stubs/bytestream.cc', 'google/protobuf/stubs/atomicops_internals_x86_msvc.cc', 'google/protobuf/stubs/atomicops_internals_x86_gcc.cc', 'google/protobuf/repeated_field.cc', 'google/protobuf/message_lite.cc', 'google/protobuf/io/zero_copy_stream_impl_lite.cc', 'google/protobuf/io/zero_copy_stream.cc', 'google/protobuf/io/coded_stream.cc', 'google/protobuf/generated_message_util.cc', 'google/protobuf/generated_message_table_driven_lite.cc', 'google/protobuf/extension_set.cc', 'google/protobuf/arenastring.cc', 'google/protobuf/arena.cc', 'google/protobuf/compiler/js/embed.cc']
 PROTO_FILES=['google/protobuf/wrappers.proto', 'google/protobuf/type.proto', 'google/protobuf/timestamp.proto', 'google/protobuf/struct.proto', 'google/protobuf/source_context.proto', 'google/protobuf/field_mask.proto', 'google/protobuf/empty.proto', 'google/protobuf/duration.proto', 'google/protobuf/descriptor.proto', 'google/protobuf/compiler/plugin.proto', 'google/protobuf/api.proto', 'google/protobuf/any.proto']
 
 CC_INCLUDE='third_party/protobuf/src'
 PROTO_INCLUDE='third_party/protobuf/src'
+
+PROTOBUF_SUBMODULE_VERSION="2761122b810fe8861004ae785cc3ab39f384d342"
diff --git a/tools/distrib/python/make_grpcio_tools.py b/tools/distrib/python/make_grpcio_tools.py
index 5bc07ff..c865f0b 100755
--- a/tools/distrib/python/make_grpcio_tools.py
+++ b/tools/distrib/python/make_grpcio_tools.py
@@ -28,7 +28,7 @@
 import uuid
 
 DEPS_FILE_CONTENT="""
-# Copyright 2016 gRPC authors.
+# 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.
@@ -48,8 +48,13 @@
 
 CC_INCLUDE={cc_include}
 PROTO_INCLUDE={proto_include}
+
+{commit_hash}
 """
 
+COMMIT_HASH_PREFIX = 'PROTOBUF_SUBMODULE_VERSION="'
+COMMIT_HASH_SUFFIX = '"'
+
 # Bazel query result prefix for expected source files in protobuf.
 PROTOBUF_CC_PREFIX = '//:src/'
 PROTOBUF_PROTO_PREFIX = '//:src/'
@@ -63,6 +68,7 @@
 
 GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT = os.path.join('third_party', 'protobuf', 'src')
 GRPC_PROTOBUF = os.path.join(GRPC_ROOT, GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT)
+GRPC_PROTOBUF_SUBMODULE_ROOT = os.path.join(GRPC_ROOT, 'third_party', 'protobuf')
 GRPC_PROTOC_PLUGINS = os.path.join(GRPC_ROOT, 'src', 'compiler')
 GRPC_PYTHON_PROTOBUF = os.path.join(GRPC_PYTHON_ROOT, 'third_party', 'protobuf',
                                     'src')
@@ -78,6 +84,14 @@
 BAZEL_DEPS_PROTOC_LIB_QUERY = '//:protoc_lib'
 BAZEL_DEPS_COMMON_PROTOS_QUERY = '//:well_known_protos'
 
+def protobuf_submodule_commit_hash():
+  """Gets the commit hash for the HEAD of the protobuf submodule currently
+     checked out."""
+  cwd = os.getcwd()
+  os.chdir(GRPC_PROTOBUF_SUBMODULE_ROOT)
+  output = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
+  os.chdir(cwd)
+  return output.splitlines()[0].strip()
 
 def bazel_query(query):
   output = subprocess.check_output([BAZEL_DEPS, query])
@@ -94,11 +108,13 @@
   proto_files = [
       name[len(PROTOBUF_PROTO_PREFIX):] for name in proto_files_output
       if name.endswith('.proto') and name.startswith(PROTOBUF_PROTO_PREFIX)]
+  commit_hash = protobuf_submodule_commit_hash()
   deps_file_content = DEPS_FILE_CONTENT.format(
       cc_files=cc_files,
       proto_files=proto_files,
       cc_include=repr(GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT),
-      proto_include=repr(GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT))
+      proto_include=repr(GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT),
+      commit_hash=COMMIT_HASH_PREFIX + commit_hash + COMMIT_HASH_SUFFIX)
   return deps_file_content
 
 def long_path(path):
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index c9c6439..3c564e2 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -1026,6 +1026,7 @@
 src/core/lib/slice/slice_hash_table.h \
 src/core/lib/slice/slice_internal.h \
 src/core/lib/slice/slice_string_helpers.h \
+src/core/lib/support/abstract.h \
 src/core/lib/support/arena.h \
 src/core/lib/support/atomic.h \
 src/core/lib/support/atomic_with_atm.h \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index b8434bb..5674124 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -1267,6 +1267,7 @@
 src/core/lib/slice/slice_internal.h \
 src/core/lib/slice/slice_string_helpers.cc \
 src/core/lib/slice/slice_string_helpers.h \
+src/core/lib/support/abstract.h \
 src/core/lib/support/alloc.cc \
 src/core/lib/support/arena.cc \
 src/core/lib/support/arena.h \
diff --git a/tools/interop_matrix/README.md b/tools/interop_matrix/README.md
index 588afe4..40c02a1 100644
--- a/tools/interop_matrix/README.md
+++ b/tools/interop_matrix/README.md
@@ -8,8 +8,8 @@
 ## Step-by-step instructions for adding a GCR image for a new release for compatibility test
 We have continuous nightly test setup to test gRPC backward compatibility between old clients and latest server.  When a gRPC developer creates a new gRPC release, s/he is also responsible to add the just-released gRPC client to the nightly test.  The steps are:
 - Add (or update) an entry in `./client_matrix.py` file to reference the github tag for the release.
-- Build new client docker image(s).  For example, for java release `v1.9.9`, do
-  - `tools/interop_matrix/create_matrix_images.py --git_checkout --release=v1.9.9 --language=java`
+- Build new client docker image(s).  For example, for C and wrapper languages release `v1.9.9`, do
+  - `tools/interop_matrix/create_matrix_images.py --git_checkout --release=v1.9.9 --language cxx csharp python ruby php`
 - Verify that the new docker image was built successfully and uploaded to GCR.  For example,
   - `gcloud beta container images list --repository gcr.io/grpc-testing` shows image repos.
   - `gcloud beta container images list-tags gcr.io/grpc-testing/grpc_interop_java_oracle8` should show an image entry with tag `v1.9.9`.
diff --git a/tools/interop_matrix/client_matrix.py b/tools/interop_matrix/client_matrix.py
index c8dbb3a..c7860f3 100644
--- a/tools/interop_matrix/client_matrix.py
+++ b/tools/interop_matrix/client_matrix.py
@@ -19,6 +19,7 @@
   return {
       'go': 'git@github.com:grpc/grpc-go.git',
       'java': 'git@github.com:grpc/grpc-java.git',
+      'node': 'git@github.com:grpc/grpc-node.git',
       # all other languages use the grpc.git repo.
   }.get(lang, 'git@github.com:grpc/grpc.git')
 
@@ -44,6 +45,7 @@
         'v1.3.9',
         'v1.4.2',
         'v1.6.6',
+        'v1.7.2',
     ],
     'go': [
         'v1.0.5',
@@ -53,6 +55,10 @@
         'v1.5.2',
         'v1.6.0',
         'v1.7.0',
+        'v1.7.1',
+        'v1.7.2',
+        'v1.7.3',
+        'v1.8.0',
     ],
     'java': [
         'v1.0.3',
@@ -63,6 +69,7 @@
         'v1.5.0',
         'v1.6.1',
         'v1.7.0',
+        'v1.8.0',
     ],
     'python': [
         'v1.0.x',
@@ -72,6 +79,24 @@
         'v1.4.2',
         'v1.6.6',
     ],
+    'python': [
+        'v1.0.x', 
+        'v1.1.4',
+        'v1.2.5',
+        'v1.3.9',
+        'v1.4.2',
+        'v1.6.6',
+        'v1.7.2',    
+    ],
+    'python': [
+        'v1.0.x',
+        'v1.1.4',
+        'v1.2.5',
+        'v1.3.9',
+        'v1.4.2',
+        'v1.6.6',
+        'v1.7.2',    
+    ],
     'node': [
         'v1.0.1',
         'v1.1.4',
@@ -79,6 +104,7 @@
         'v1.3.9',
         'v1.4.2',
         'v1.6.6',
+        'v1.7.1',
     ],
     'ruby': [
         # Ruby v1.0.x doesn't have the fix #8914, therefore not supported.
@@ -87,6 +113,7 @@
         'v1.3.9',
         'v1.4.2',
         'v1.6.6',
+        'v1.7.2',
     ],
     'php': [
         'v1.0.1',
@@ -95,6 +122,7 @@
         'v1.3.9',
         'v1.4.2',
         'v1.6.6',
+        'v1.7.2',
     ],
    'csharp': [
         #'v1.0.1',
@@ -103,5 +131,6 @@
         'v1.3.9',
         'v1.4.2',
         'v1.6.6',
+        'v1.7.2',
     ],
 }
diff --git a/tools/interop_matrix/create_matrix_images.py b/tools/interop_matrix/create_matrix_images.py
index 21040ea..493a7d5 100755
--- a/tools/interop_matrix/create_matrix_images.py
+++ b/tools/interop_matrix/create_matrix_images.py
@@ -77,6 +77,15 @@
                   action='store_true',
                   help='keep the created local images after uploading to GCR')
 
+argp.add_argument('--reuse_git_root',
+                  default=False,
+                  action='store_const',
+                  const=True,                  
+                  help='reuse the repo dir. If False, the existing git root '
+                  'directory will removed before a clean checkout, because '
+                  'reusing the repo can cause git checkout error if you switch '
+                  'between releases.')
+
 
 args = argp.parse_args()
 
@@ -133,7 +142,7 @@
   }
   build_env.update(env)
   build_job = jobset.JobSpec(
-          cmdline=[os.path.join(stack_base, _IMAGE_BUILDER)],
+          cmdline=[_IMAGE_BUILDER],
           environ=build_env,
           shortname='build_docker_%s' % runtime,
           timeout_seconds=30*60)
@@ -227,6 +236,11 @@
   repo_dir = os.path.splitext(os.path.basename(repo))[0]
   stack_base = os.path.join(args.git_checkout_root, repo_dir)
 
+  # Clean up leftover repo dir if necessary.
+  if not args.reuse_git_root and os.path.exists(stack_base):
+    jobset.message('START', 'Removing git checkout root.', do_newline=True)
+    shutil.rmtree(stack_base)
+
   if not os.path.exists(stack_base):
     subprocess.check_call(['git', 'clone', '--recursive', repo],
                           cwd=os.path.dirname(stack_base))
diff --git a/tools/interop_matrix/testcases/node__v1.0.1 b/tools/interop_matrix/testcases/node__v1.0.1
index fca9821..6faf321 100644
--- a/tools/interop_matrix/testcases/node__v1.0.1
+++ b/tools/interop_matrix/testcases/node__v1.0.1
@@ -1,21 +1,21 @@
 #!/bin/bash
 echo "Testing ${docker_image:=grpc_interop_node:a53aa5e3-b548-4566-b5a8-6d15c1315b32}"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_00c688f2-57da-4023-89f3-46b1f7b5869f $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=large_unary"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_d86705d8-14ea-4024-90b6-de74d6e8d19c $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_unary"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_ebaafacd-1d82-4a75-bea1-a5c64e01fcaf $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=ping_pong"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_5125241a-fbf6-4c1c-895b-026a5a41f02f $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_stream"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_3d5e5b82-7205-4eba-b775-8122f05a4760 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=client_streaming"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_16fa0b49-2083-4932-8f26-79cfdffec940 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=server_streaming"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_23cee670-2d8d-4f5c-8893-c3c43da8b03a $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_begin"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_7b6a9454-e3b6-4993-b542-468e268930aa $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_first_response"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_5a7109ed-c065-4b62-98f4-b3ed8f385762 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test.sandbox.googleapis.com --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=timeout_on_sleeping_server"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_1e0ba8dd-4dc1-431b-b202-529e7ace5d1d $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=large_unary"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_649de56a-e722-4a59-b265-e9c0e871f068 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_unary"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_8be512a2-5e1c-4858-8cfc-82f99b678b76 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=ping_pong"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_d19b6333-e528-48b5-8421-2ae3f7ce9dab $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_stream"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_4749c80a-21c5-4d81-9df5-3c46ba6480cd $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=client_streaming"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_b7bee545-4857-4269-a1f2-9553dfc7e4b8 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=server_streaming"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_4e96db87-6f48-426d-a7f0-97f3a5b6b3f4 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_begin"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_31a1d58c-f8bf-460a-af60-28969ecaaf80 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_first_response"
-docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_ce64b147-bcfa-44b2-a2f7-4a485380ce30 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=grpc-test4.sandbox.googleapis.com --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=timeout_on_sleeping_server"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_00c688f2-57da-4023-89f3-46b1f7b5869f $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=large_unary"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_d86705d8-14ea-4024-90b6-de74d6e8d19c $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_unary"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_ebaafacd-1d82-4a75-bea1-a5c64e01fcaf $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=ping_pong"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_5125241a-fbf6-4c1c-895b-026a5a41f02f $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_stream"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_3d5e5b82-7205-4eba-b775-8122f05a4760 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=client_streaming"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_16fa0b49-2083-4932-8f26-79cfdffec940 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=server_streaming"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_23cee670-2d8d-4f5c-8893-c3c43da8b03a $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_begin"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_7b6a9454-e3b6-4993-b542-468e268930aa $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_first_response"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_5a7109ed-c065-4b62-98f4-b3ed8f385762 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=timeout_on_sleeping_server"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_1e0ba8dd-4dc1-431b-b202-529e7ace5d1d $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=large_unary"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_649de56a-e722-4a59-b265-e9c0e871f068 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_unary"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_8be512a2-5e1c-4858-8cfc-82f99b678b76 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=ping_pong"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_d19b6333-e528-48b5-8421-2ae3f7ce9dab $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=empty_stream"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_4749c80a-21c5-4d81-9df5-3c46ba6480cd $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=client_streaming"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_b7bee545-4857-4269-a1f2-9553dfc7e4b8 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=server_streaming"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_4e96db87-6f48-426d-a7f0-97f3a5b6b3f4 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_begin"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_31a1d58c-f8bf-460a-af60-28969ecaaf80 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=cancel_after_first_response"
+docker run -i --rm=true -w /var/local/git/grpc --net=host --name interop_client_node_ce64b147-bcfa-44b2-a2f7-4a485380ce30 $docker_image bash -l -c "node src/node/interop/interop_client.js --server_host=216.239.32.254 --server_host_override=grpc-test4.sandbox.googleapis.com --server_port=443 --use_tls=true --test_case=timeout_on_sleeping_server"
 
diff --git a/tools/run_tests/generated/configs.json b/tools/run_tests/generated/configs.json
index abbe76d..fee8290 100644
--- a/tools/run_tests/generated/configs.json
+++ b/tools/run_tests/generated/configs.json
@@ -20,7 +20,10 @@
     }
   }, 
   {
-    "config": "msan"
+    "config": "msan", 
+    "environ": {
+      "MSAN_OPTIONS": "poison_in_dtor=1"
+    }
   }, 
   {
     "config": "basicprof"
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index 153909d..8a7d54e 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -756,6 +756,21 @@
     "headers": [], 
     "is_filegroup": false, 
     "language": "c", 
+    "name": "gpr_manual_constructor_test", 
+    "src": [
+      "test/core/support/manual_constructor_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
     "name": "gpr_mpscq_test", 
     "src": [
       "test/core/support/mpscq_test.cc"
@@ -7831,6 +7846,7 @@
       "include/grpc/support/tls_pthread.h", 
       "include/grpc/support/useful.h", 
       "src/core/lib/profiling/timers.h", 
+      "src/core/lib/support/abstract.h", 
       "src/core/lib/support/arena.h", 
       "src/core/lib/support/atomic.h", 
       "src/core/lib/support/atomic_with_atm.h", 
@@ -7879,6 +7895,7 @@
       "include/grpc/support/tls_pthread.h", 
       "include/grpc/support/useful.h", 
       "src/core/lib/profiling/timers.h", 
+      "src/core/lib/support/abstract.h", 
       "src/core/lib/support/arena.h", 
       "src/core/lib/support/atomic.h", 
       "src/core/lib/support/atomic_with_atm.h", 
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index 78db31e..5b9e309 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -920,6 +920,30 @@
       "posix", 
       "windows"
     ], 
+    "cpu_cost": 3, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c", 
+    "name": "gpr_manual_constructor_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [], 
+    "benchmark": false, 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
     "cpu_cost": 30, 
     "exclude_configs": [], 
     "exclude_iomgrs": [], 
diff --git a/tools/run_tests/sanity/check_bazel_workspace.py b/tools/run_tests/sanity/check_bazel_workspace.py
new file mode 100755
index 0000000..776c78b
--- /dev/null
+++ b/tools/run_tests/sanity/check_bazel_workspace.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+# 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.
+
+from __future__ import print_function
+
+import ast
+import os
+import re
+import subprocess
+import sys
+
+os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '../../..'))
+
+git_hash_pattern = re.compile('[0-9a-f]{40}')
+
+# Parse git hashes from submodules
+git_submodules = subprocess.check_output('git submodule', shell=True).strip().split('\n')
+git_submodule_hashes = {re.search(git_hash_pattern, s).group() for s in git_submodules}
+
+# Parse git hashes from Bazel WORKSPACE {new_}http_archive rules
+with open('WORKSPACE', 'r') as f:
+  workspace_rules = [expr.value for expr in ast.parse(f.read()).body]
+
+http_archive_rules = [rule for rule in workspace_rules if rule.func.id.endswith('http_archive')]
+archive_urls = [kw.value.s for rule in http_archive_rules for kw in rule.keywords if kw.arg == 'url']
+workspace_git_hashes = {re.search(git_hash_pattern, url).group() for url in archive_urls}
+
+# Validate the equivalence of the git submodules and Bazel git dependencies. The
+# condition we impose is that there is a git submodule for every dependency in
+# the workspace, but not necessarily conversely. E.g. Bloaty is a dependency
+# not used by any of the targets built by Bazel.
+if len(workspace_git_hashes - git_submodule_hashes) > 0:
+    print("Found discrepancies between git submodules and Bazel WORKSPACE dependencies")
+    sys.exit(1)
+
+sys.exit(0)
diff --git a/tools/run_tests/sanity/check_submodules.sh b/tools/run_tests/sanity/check_submodules.sh
index 1f7b078..b573d21 100755
--- a/tools/run_tests/sanity/check_submodules.sh
+++ b/tools/run_tests/sanity/check_submodules.sh
@@ -31,7 +31,7 @@
  886e7d75368e3f4fab3f4d0d3584e4abfc557755 third_party/boringssl-with-bazel (version_for_cocoapods_7.0-857-g886e7d7)
  30dbc81fb5ffdc98ea9b14b1918bfe4e8779b26e third_party/gflags (v2.2.0)
  ec44c6c1675c25b9827aacd08c02433cccde7780 third_party/googletest (release-1.8.0)
- 80a37e0782d2d702d52234b62dd4b9ec74fd2c95 third_party/protobuf (v3.4.0)
+ 2761122b810fe8861004ae785cc3ab39f384d342 third_party/protobuf (v3.5.0)
  cacf7f1d4e3d44d871b605da3b647f07d718623f third_party/zlib (v1.2.11)
  3be1924221e1326df520f8498d704a5c4c8d0cce third_party/cares/cares (cares-1_13_0)
  73594cde8c9a52a102c4341c244c833aa61b9c06 third_party/bloaty
diff --git a/tools/run_tests/sanity/sanity_tests.yaml b/tools/run_tests/sanity/sanity_tests.yaml
index 3ca5c1b..81eec4d 100644
--- a/tools/run_tests/sanity/sanity_tests.yaml
+++ b/tools/run_tests/sanity/sanity_tests.yaml
@@ -1,4 +1,5 @@
 # a set of tests that are run in parallel for sanity tests
+- script: tools/run_tests/sanity/check_bazel_workspace.py
 - script: tools/run_tests/sanity/check_cache_mk.sh
 - script: tools/run_tests/sanity/check_owners.sh
 - script: tools/run_tests/sanity/check_sources_and_headers.py