Merge pull request #11264 from smulikHakipod/master

Compilation without ALPN support does not work
diff --git a/BUILD b/BUILD
index cc5fd3e..2070ce3 100644
--- a/BUILD
+++ b/BUILD
@@ -51,9 +51,9 @@
 # This should be updated along with build.yaml
 g_stands_for = "gregarious"
 
-core_version = "3.0.0-dev"
+core_version = "4.0.0-pre1"
 
-version = "1.4.0-dev"
+version = "1.4.0-pre1"
 
 grpc_cc_library(
     name = "gpr",
@@ -282,6 +282,7 @@
         "src/core/ext/census/grpc_filter.c",
         "src/core/ext/census/grpc_plugin.c",
         "src/core/ext/census/initialize.c",
+        "src/core/ext/census/intrusive_hash_map.c",
         "src/core/ext/census/mlog.c",
         "src/core/ext/census/operation.c",
         "src/core/ext/census/placeholders.c",
@@ -297,6 +298,8 @@
         "src/core/ext/census/gen/census.pb.h",
         "src/core/ext/census/gen/trace_context.pb.h",
         "src/core/ext/census/grpc_filter.h",
+        "src/core/ext/census/intrusive_hash_map.h",
+        "src/core/ext/census/intrusive_hash_map_internal.h",
         "src/core/ext/census/mlog.h",
         "src/core/ext/census/resource.h",
         "src/core/ext/census/rpc_metric_id.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1a23545..acae19e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,7 +39,7 @@
 cmake_minimum_required(VERSION 2.8)
 
 set(PACKAGE_NAME      "grpc")
-set(PACKAGE_VERSION   "1.4.0-dev")
+set(PACKAGE_VERSION   "1.4.0-pre1")
 set(PACKAGE_STRING    "${PACKAGE_NAME} ${PACKAGE_VERSION}")
 set(PACKAGE_TARNAME   "${PACKAGE_NAME}-${PACKAGE_VERSION}")
 set(PACKAGE_BUGREPORT "https://github.com/grpc/grpc/issues/")
@@ -375,6 +375,7 @@
 add_dependencies(buildtests_c bin_decoder_test)
 add_dependencies(buildtests_c bin_encoder_test)
 add_dependencies(buildtests_c census_context_test)
+add_dependencies(buildtests_c census_intrusive_hash_map_test)
 add_dependencies(buildtests_c census_resource_test)
 add_dependencies(buildtests_c census_trace_context_test)
 add_dependencies(buildtests_c channel_create_test)
@@ -481,6 +482,7 @@
 add_dependencies(buildtests_c multiple_server_queues_test)
 add_dependencies(buildtests_c murmur_hash_test)
 add_dependencies(buildtests_c no_server_test)
+add_dependencies(buildtests_c num_external_connectivity_watchers_test)
 add_dependencies(buildtests_c parse_address_test)
 add_dependencies(buildtests_c percent_encoding_test)
 if(_gRPC_PLATFORM_LINUX)
@@ -1157,6 +1159,7 @@
   src/core/ext/census/grpc_filter.c
   src/core/ext/census/grpc_plugin.c
   src/core/ext/census/initialize.c
+  src/core/ext/census/intrusive_hash_map.c
   src/core/ext/census/mlog.c
   src/core/ext/census/operation.c
   src/core/ext/census/placeholders.c
@@ -2048,6 +2051,7 @@
   src/core/ext/census/grpc_filter.c
   src/core/ext/census/grpc_plugin.c
   src/core/ext/census/initialize.c
+  src/core/ext/census/intrusive_hash_map.c
   src/core/ext/census/mlog.c
   src/core/ext/census/operation.c
   src/core/ext/census/placeholders.c
@@ -2659,6 +2663,7 @@
   src/core/ext/census/grpc_filter.c
   src/core/ext/census/grpc_plugin.c
   src/core/ext/census/initialize.c
+  src/core/ext/census/intrusive_hash_map.c
   src/core/ext/census/mlog.c
   src/core/ext/census/operation.c
   src/core/ext/census/placeholders.c
@@ -4769,6 +4774,37 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(census_intrusive_hash_map_test
+  test/core/census/intrusive_hash_map_test.c
+)
+
+
+target_include_directories(census_intrusive_hash_map_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_BUILD_INCLUDE_DIR}
+  PRIVATE ${CARES_INCLUDE_DIR}
+  PRIVATE ${CARES_PLATFORM_INCLUDE_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/cares/cares
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/gflags/include
+)
+
+target_link_libraries(census_intrusive_hash_map_test
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_test_util
+  grpc
+  gpr_test_util
+  gpr
+)
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(census_resource_test
   test/core/census/resource_test.c
 )
@@ -7488,6 +7524,37 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(num_external_connectivity_watchers_test
+  test/core/surface/num_external_connectivity_watchers_test.c
+)
+
+
+target_include_directories(num_external_connectivity_watchers_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_BUILD_INCLUDE_DIR}
+  PRIVATE ${CARES_INCLUDE_DIR}
+  PRIVATE ${CARES_PLATFORM_INCLUDE_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/cares/cares
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/gflags/include
+)
+
+target_link_libraries(num_external_connectivity_watchers_test
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_test_util
+  grpc
+  gpr_test_util
+  gpr
+)
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(parse_address_test
   test/core/client_channel/parse_address_test.c
 )
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 56bb4b6..e9c5fe2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,74 +1,54 @@
 # How to contribute
 
-We definitely welcome patches and contribution to grpc! Here is some guideline
-and information about how to do so.
+We definitely welcome your patches and contributions to gRPC!
 
-## Getting started
+If you are new to github, please start by reading [Pull Request howto](https://help.github.com/articles/about-pull-requests/)
 
-### Legal requirements
+## Legal requirements
 
 In order to protect both you and ourselves, you will need to sign the
 [Contributor License Agreement](https://cla.developers.google.com/clas).
 
-### Technical requirements
+## Running tests
 
-You will need several tools to work with this repository. In addition to all of
-the packages described in the [INSTALL](INSTALL.md) file, you will also need
-python, and the mako template renderer. To install the latter, using pip, one
-should simply be able to do `pip install mako`.
+Use `tools/run_tests/run_tests.py` script to run the unit tests.
+See [tools/run_tests](tools/run_tests) for how to run tests for a given language.
 
-In order to run all of the tests we provide, you will need valgrind and clang.
-More specifically, under debian, you will need the package libc++-dev to
-properly run all the tests.
+Prerequisites for building and running tests are listed in [INSTALL.md](INSTALL.md)
+and in `src/YOUR-LANGUAGE` (e.g. `src/csharp`)
 
-Compiling and running grpc C++ tests depend on protobuf 3.0.0, gtest and gflags.
-Although gflags is provided in third_party, you will need to manually install
-that dependency on your system to run these tests. Under a Debian or Ubuntu
-system, you can install the gtests and gflags packages using apt-get:
+## Generated project files
 
-```sh
- $ [sudo] apt-get install libgflags-dev libgtest-dev
-```
+To ease maintenance of language- and platform- specific build systems,
+many projects files are generated using templates and should not be edited
+by hand.
+Run `tools/buildgen/generate_projects.sh` to regenerate.
+See [templates](templates) for details.
 
-If you are planning to work on any of the languages other than C and C++, you
-will also need their appropriate development environments.
+As a rule of thumb, if you see the "sanity tests" failing you've most likely edited generated files or you didn't regenerate the projects properly (or your code formatting doesn't match our code style).
 
-If you want to work under Windows, we recommend the use of Visual Studio 2013.
-The [Community or Express editions](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx)
-are free and suitable for developing with grpc. Note however that our test
-environment and tools are available for Unix environments only at the moment.
+## Guidelines for Pull Requests
+How to get your contributions merged smoothly and quickly.
+ 
+- Create **small PRs** that are narrowly focused on **addressing a single concern**. We often times receive PRs that are trying to fix several things at a time, but only one fix is considered acceptable, nothing gets merged and both author's & review's time is wasted. Create more PRs to address different concerns and everyone will be happy.
+ 
+- For speculative changes, consider opening an issue and discussing it first. If you are suggesting a behavioral or API change, consider starting with a [gRFC proposal](https://github.com/grpc/proposal). 
+ 
+- Provide a good **PR description** as a record of **what** change is being made and **why** it was made. Link to a github issue if it exists.
+ 
+- Don't fix code style and formatting unless you are already changing that line to address an issue. PRs with irrelevant changes won't be merged. If you do want to fix formatting or style, do that in a separate PR.
+ 
+- Unless your PR is trivial, you should expect there will be reviewer comments that you'll need to address before merging. We expect you to be reasonably responsive to those comments, otherwise the PR will be closed after 2-3 weeks of inactivity.
+ 
+- Maintain **clean commit history** and use **meaningful commit messages**. PRs with messy commit history are difficult to review and won't be merged. Use `rebase -i upstream/master` to curate your commit history and/or to bring in latest changes from master (but avoid rebasing in the middle of a code review).
+ 
+- Keep your PR up to date with upstream/master (if there are merge conflicts, we can't really merge your change).
+ 
+- if you are regenerating the projects using `tools/buildgen/generate_projects.sh`, make changes to generated files a separate commit with commit message `regenerate projects`. Mixing changes to generated and hand-written files make your PR difficult to review.
+ 
+- **All tests need to be passing** before your change can be merged. We recommend you **run tests locally** before creating your PR to catch breakages early on (see [tools/run_tests](tools/run_tests). Ultimately, the green signal will be provided by our testing infrastructure. The reviewer will help you if there are test failures that seem not related to the change you are making.
+ 
+- Exceptions to the rules can be made if there's a compelling reason for doing so.
 
-## Testing your changes
 
-We provide a tool to help run the suite of tests in various environments.
-In order to run most of the available tests, one would need to run:
 
-`./tools/run_tests/run_tests.py`
-
-If you want to run tests for any of the languages {c, c++, csharp, node, objc, php, python, ruby}, do this:
-
-`./tools/run_tests/run_tests.py -l <lang>`
-
-To know about the list of available commands, do this:
-
-`./tools/run_tests/run_tests.py -h`
-
-If you are running tests for ObjC on osx, follow these steps before running tests:
-* install Xcode command-line tools by running
-`sudo xcode-select --install`
-* install macports from https://www.macports.org/install.php
-* install autoconf, automake, libtool, gflags, cmake using macports
-* restart your terminal window or run source ~/.bash_profile to pick up the new PATH changes.
-
-## Adding or removing source code
-
-Each language uses its own build system to work. Currently, the root's Makefile
-and the Visual Studio project files are building only the C and C++ source code.
-In order to ease the maintenance of these files, we have a
-template system. Please do not contribute manual changes to any of the generated
-files. Instead, modify the template files, or the build.yaml file, and
-re-generate the project files using the following command:
-
-`./tools/buildgen/generate_projects.sh`
-
-You'll find more information about this in the [templates](templates) folder.
diff --git a/Makefile b/Makefile
index 5e5cd95..c87fd19 100644
--- a/Makefile
+++ b/Makefile
@@ -422,9 +422,9 @@
 Q = @
 endif
 
-CORE_VERSION = 4.0.0-dev
-CPP_VERSION = 1.4.0-dev
-CSHARP_VERSION = 1.4.0-dev
+CORE_VERSION = 4.0.0-pre1
+CPP_VERSION = 1.4.0-pre1
+CSHARP_VERSION = 1.4.0-pre1
 
 CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
 CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
@@ -967,6 +967,7 @@
 bin_decoder_test: $(BINDIR)/$(CONFIG)/bin_decoder_test
 bin_encoder_test: $(BINDIR)/$(CONFIG)/bin_encoder_test
 census_context_test: $(BINDIR)/$(CONFIG)/census_context_test
+census_intrusive_hash_map_test: $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test
 census_resource_test: $(BINDIR)/$(CONFIG)/census_resource_test
 census_trace_context_test: $(BINDIR)/$(CONFIG)/census_trace_context_test
 channel_create_test: $(BINDIR)/$(CONFIG)/channel_create_test
@@ -1062,6 +1063,7 @@
 nanopb_fuzzer_response_test: $(BINDIR)/$(CONFIG)/nanopb_fuzzer_response_test
 nanopb_fuzzer_serverlist_test: $(BINDIR)/$(CONFIG)/nanopb_fuzzer_serverlist_test
 no_server_test: $(BINDIR)/$(CONFIG)/no_server_test
+num_external_connectivity_watchers_test: $(BINDIR)/$(CONFIG)/num_external_connectivity_watchers_test
 parse_address_test: $(BINDIR)/$(CONFIG)/parse_address_test
 percent_decode_fuzzer: $(BINDIR)/$(CONFIG)/percent_decode_fuzzer
 percent_encode_fuzzer: $(BINDIR)/$(CONFIG)/percent_encode_fuzzer
@@ -1360,6 +1362,7 @@
   $(BINDIR)/$(CONFIG)/bin_decoder_test \
   $(BINDIR)/$(CONFIG)/bin_encoder_test \
   $(BINDIR)/$(CONFIG)/census_context_test \
+  $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test \
   $(BINDIR)/$(CONFIG)/census_resource_test \
   $(BINDIR)/$(CONFIG)/census_trace_context_test \
   $(BINDIR)/$(CONFIG)/channel_create_test \
@@ -1440,6 +1443,7 @@
   $(BINDIR)/$(CONFIG)/multiple_server_queues_test \
   $(BINDIR)/$(CONFIG)/murmur_hash_test \
   $(BINDIR)/$(CONFIG)/no_server_test \
+  $(BINDIR)/$(CONFIG)/num_external_connectivity_watchers_test \
   $(BINDIR)/$(CONFIG)/parse_address_test \
   $(BINDIR)/$(CONFIG)/percent_encoding_test \
   $(BINDIR)/$(CONFIG)/pollset_set_test \
@@ -1763,6 +1767,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/bin_encoder_test || ( echo test bin_encoder_test failed ; exit 1 )
 	$(E) "[RUN]     Testing census_context_test"
 	$(Q) $(BINDIR)/$(CONFIG)/census_context_test || ( echo test census_context_test failed ; exit 1 )
+	$(E) "[RUN]     Testing census_intrusive_hash_map_test"
+	$(Q) $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test || ( echo test census_intrusive_hash_map_test failed ; exit 1 )
 	$(E) "[RUN]     Testing census_resource_test"
 	$(Q) $(BINDIR)/$(CONFIG)/census_resource_test || ( echo test census_resource_test failed ; exit 1 )
 	$(E) "[RUN]     Testing census_trace_context_test"
@@ -1907,6 +1913,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/murmur_hash_test || ( echo test murmur_hash_test failed ; exit 1 )
 	$(E) "[RUN]     Testing no_server_test"
 	$(Q) $(BINDIR)/$(CONFIG)/no_server_test || ( echo test no_server_test failed ; exit 1 )
+	$(E) "[RUN]     Testing num_external_connectivity_watchers_test"
+	$(Q) $(BINDIR)/$(CONFIG)/num_external_connectivity_watchers_test || ( echo test num_external_connectivity_watchers_test failed ; exit 1 )
 	$(E) "[RUN]     Testing parse_address_test"
 	$(Q) $(BINDIR)/$(CONFIG)/parse_address_test || ( echo test parse_address_test failed ; exit 1 )
 	$(E) "[RUN]     Testing percent_encoding_test"
@@ -3134,6 +3142,7 @@
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
@@ -3994,6 +4003,7 @@
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
@@ -4591,6 +4601,7 @@
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
@@ -8687,6 +8698,38 @@
 endif
 
 
+CENSUS_INTRUSIVE_HASH_MAP_TEST_SRC = \
+    test/core/census/intrusive_hash_map_test.c \
+
+CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(CENSUS_INTRUSIVE_HASH_MAP_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test: $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/census_intrusive_hash_map_test
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/census/intrusive_hash_map_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_census_intrusive_hash_map_test: $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(CENSUS_INTRUSIVE_HASH_MAP_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 CENSUS_RESOURCE_TEST_SRC = \
     test/core/census/resource_test.c \
 
@@ -11727,6 +11770,38 @@
 endif
 
 
+NUM_EXTERNAL_CONNECTIVITY_WATCHERS_TEST_SRC = \
+    test/core/surface/num_external_connectivity_watchers_test.c \
+
+NUM_EXTERNAL_CONNECTIVITY_WATCHERS_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(NUM_EXTERNAL_CONNECTIVITY_WATCHERS_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/num_external_connectivity_watchers_test: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/num_external_connectivity_watchers_test: $(NUM_EXTERNAL_CONNECTIVITY_WATCHERS_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(NUM_EXTERNAL_CONNECTIVITY_WATCHERS_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/num_external_connectivity_watchers_test
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/surface/num_external_connectivity_watchers_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_num_external_connectivity_watchers_test: $(NUM_EXTERNAL_CONNECTIVITY_WATCHERS_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(NUM_EXTERNAL_CONNECTIVITY_WATCHERS_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 PARSE_ADDRESS_TEST_SRC = \
     test/core/client_channel/parse_address_test.c \
 
diff --git a/Rakefile b/Rakefile
index 7f8d3a2..6687058 100755
--- a/Rakefile
+++ b/Rakefile
@@ -93,6 +93,7 @@
 
   [ w64, w32 ].each do |opt|
     env_comp = "CC=#{opt[:cross]}-gcc "
+    env_comp += "CXX=#{opt[:cross]}-g++ "
     env_comp += "LD=#{opt[:cross]}-gcc "
     docker_for_windows "gem update --system && #{env} #{env_comp} make -j #{out} && #{opt[:cross]}-strip -x -S #{out} && cp #{out} #{opt[:out]}"
   end
diff --git a/binding.gyp b/binding.gyp
index e7ddc2b..853c1e0 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -888,6 +888,7 @@
         'src/core/ext/census/grpc_filter.c',
         'src/core/ext/census/grpc_plugin.c',
         'src/core/ext/census/initialize.c',
+        'src/core/ext/census/intrusive_hash_map.c',
         'src/core/ext/census/mlog.c',
         'src/core/ext/census/operation.c',
         'src/core/ext/census/placeholders.c',
diff --git a/build.yaml b/build.yaml
index 30c7a8e..fd43d64 100644
--- a/build.yaml
+++ b/build.yaml
@@ -12,9 +12,9 @@
   '#08': Use "-preN" suffixes to identify pre-release versions
   '#09': Per-language overrides are possible with (eg) ruby_version tag here
   '#10': See the expand_version.py for all the quirks here
-  core_version: 4.0.0-dev
+  core_version: 4.0.0-pre1
   g_stands_for: gregarious
-  version: 1.4.0-dev
+  version: 1.4.0-pre1
 filegroups:
 - name: census
   public_headers:
@@ -27,6 +27,8 @@
   - src/core/ext/census/gen/census.pb.h
   - src/core/ext/census/gen/trace_context.pb.h
   - src/core/ext/census/grpc_filter.h
+  - src/core/ext/census/intrusive_hash_map.h
+  - src/core/ext/census/intrusive_hash_map_internal.h
   - src/core/ext/census/mlog.h
   - src/core/ext/census/resource.h
   - src/core/ext/census/rpc_metric_id.h
@@ -45,6 +47,7 @@
   - src/core/ext/census/grpc_filter.c
   - src/core/ext/census/grpc_plugin.c
   - src/core/ext/census/initialize.c
+  - src/core/ext/census/intrusive_hash_map.c
   - src/core/ext/census/mlog.c
   - src/core/ext/census/operation.c
   - src/core/ext/census/placeholders.c
@@ -1676,6 +1679,16 @@
   - grpc
   - gpr_test_util
   - gpr
+- name: census_intrusive_hash_map_test
+  build: test
+  language: c
+  src:
+  - test/core/census/intrusive_hash_map_test.c
+  deps:
+  - grpc_test_util
+  - grpc
+  - gpr_test_util
+  - gpr
 - name: census_resource_test
   build: test
   language: c
@@ -2694,6 +2707,18 @@
   - grpc
   - gpr_test_util
   - gpr
+- name: num_external_connectivity_watchers_test
+  build: test
+  language: c
+  src:
+  - test/core/surface/num_external_connectivity_watchers_test.c
+  deps:
+  - grpc_test_util
+  - grpc
+  - gpr_test_util
+  - gpr
+  exclude_iomgrs:
+  - uv
 - name: parse_address_test
   build: test
   language: c
@@ -4630,6 +4655,7 @@
   - grpc
   - gpr
   - boringssl
+  - z
   headers:
   - src/php/ext/grpc/byte_buffer.h
   - src/php/ext/grpc/call.h
diff --git a/config.m4 b/config.m4
index ec463b2..3b9cac1 100644
--- a/config.m4
+++ b/config.m4
@@ -322,6 +322,7 @@
     src/core/ext/census/grpc_filter.c \
     src/core/ext/census/grpc_plugin.c \
     src/core/ext/census/initialize.c \
+    src/core/ext/census/intrusive_hash_map.c \
     src/core/ext/census/mlog.c \
     src/core/ext/census/operation.c \
     src/core/ext/census/placeholders.c \
diff --git a/config.w32 b/config.w32
new file mode 100644
index 0000000..c9f30d6
--- /dev/null
+++ b/config.w32
@@ -0,0 +1,772 @@
+// $Id$
+// vim:ft=javascript
+
+ARG_WITH("grpc", "grpc support", "no");
+
+if (PHP_GRPC != "no") {
+
+  grpc_source =
+    "src\\php\\ext\\grpc\\byte_buffer.c " +
+    "src\\php\\ext\\grpc\\call.c " +
+    "src\\php\\ext\\grpc\\call_credentials.c " +
+    "src\\php\\ext\\grpc\\channel.c " +
+    "src\\php\\ext\\grpc\\channel_credentials.c " +
+    "src\\php\\ext\\grpc\\completion_queue.c " +
+    "src\\php\\ext\\grpc\\php_grpc.c " +
+    "src\\php\\ext\\grpc\\server.c " +
+    "src\\php\\ext\\grpc\\server_credentials.c " +
+    "src\\php\\ext\\grpc\\timeval.c " +
+    "src\\core\\lib\\profiling\\basic_timers.c " +
+    "src\\core\\lib\\profiling\\stap_timers.c " +
+    "src\\core\\lib\\support\\alloc.c " +
+    "src\\core\\lib\\support\\arena.c " +
+    "src\\core\\lib\\support\\atm.c " +
+    "src\\core\\lib\\support\\avl.c " +
+    "src\\core\\lib\\support\\backoff.c " +
+    "src\\core\\lib\\support\\cmdline.c " +
+    "src\\core\\lib\\support\\cpu_iphone.c " +
+    "src\\core\\lib\\support\\cpu_linux.c " +
+    "src\\core\\lib\\support\\cpu_posix.c " +
+    "src\\core\\lib\\support\\cpu_windows.c " +
+    "src\\core\\lib\\support\\env_linux.c " +
+    "src\\core\\lib\\support\\env_posix.c " +
+    "src\\core\\lib\\support\\env_windows.c " +
+    "src\\core\\lib\\support\\histogram.c " +
+    "src\\core\\lib\\support\\host_port.c " +
+    "src\\core\\lib\\support\\log.c " +
+    "src\\core\\lib\\support\\log_android.c " +
+    "src\\core\\lib\\support\\log_linux.c " +
+    "src\\core\\lib\\support\\log_posix.c " +
+    "src\\core\\lib\\support\\log_windows.c " +
+    "src\\core\\lib\\support\\mpscq.c " +
+    "src\\core\\lib\\support\\murmur_hash.c " +
+    "src\\core\\lib\\support\\stack_lockfree.c " +
+    "src\\core\\lib\\support\\string.c " +
+    "src\\core\\lib\\support\\string_posix.c " +
+    "src\\core\\lib\\support\\string_util_windows.c " +
+    "src\\core\\lib\\support\\string_windows.c " +
+    "src\\core\\lib\\support\\subprocess_posix.c " +
+    "src\\core\\lib\\support\\subprocess_windows.c " +
+    "src\\core\\lib\\support\\sync.c " +
+    "src\\core\\lib\\support\\sync_posix.c " +
+    "src\\core\\lib\\support\\sync_windows.c " +
+    "src\\core\\lib\\support\\thd.c " +
+    "src\\core\\lib\\support\\thd_posix.c " +
+    "src\\core\\lib\\support\\thd_windows.c " +
+    "src\\core\\lib\\support\\time.c " +
+    "src\\core\\lib\\support\\time_posix.c " +
+    "src\\core\\lib\\support\\time_precise.c " +
+    "src\\core\\lib\\support\\time_windows.c " +
+    "src\\core\\lib\\support\\tls_pthread.c " +
+    "src\\core\\lib\\support\\tmpfile_msys.c " +
+    "src\\core\\lib\\support\\tmpfile_posix.c " +
+    "src\\core\\lib\\support\\tmpfile_windows.c " +
+    "src\\core\\lib\\support\\wrap_memcpy.c " +
+    "src\\core\\lib\\surface\\init.c " +
+    "src\\core\\lib\\channel\\channel_args.c " +
+    "src\\core\\lib\\channel\\channel_stack.c " +
+    "src\\core\\lib\\channel\\channel_stack_builder.c " +
+    "src\\core\\lib\\channel\\connected_channel.c " +
+    "src\\core\\lib\\channel\\handshaker.c " +
+    "src\\core\\lib\\channel\\handshaker_factory.c " +
+    "src\\core\\lib\\channel\\handshaker_registry.c " +
+    "src\\core\\lib\\compression\\compression.c " +
+    "src\\core\\lib\\compression\\message_compress.c " +
+    "src\\core\\lib\\http\\format_request.c " +
+    "src\\core\\lib\\http\\httpcli.c " +
+    "src\\core\\lib\\http\\parser.c " +
+    "src\\core\\lib\\iomgr\\closure.c " +
+    "src\\core\\lib\\iomgr\\combiner.c " +
+    "src\\core\\lib\\iomgr\\endpoint.c " +
+    "src\\core\\lib\\iomgr\\endpoint_pair_posix.c " +
+    "src\\core\\lib\\iomgr\\endpoint_pair_uv.c " +
+    "src\\core\\lib\\iomgr\\endpoint_pair_windows.c " +
+    "src\\core\\lib\\iomgr\\error.c " +
+    "src\\core\\lib\\iomgr\\ev_epoll1_linux.c " +
+    "src\\core\\lib\\iomgr\\ev_epoll_limited_pollers_linux.c " +
+    "src\\core\\lib\\iomgr\\ev_epoll_thread_pool_linux.c " +
+    "src\\core\\lib\\iomgr\\ev_epollex_linux.c " +
+    "src\\core\\lib\\iomgr\\ev_epollsig_linux.c " +
+    "src\\core\\lib\\iomgr\\ev_poll_posix.c " +
+    "src\\core\\lib\\iomgr\\ev_posix.c " +
+    "src\\core\\lib\\iomgr\\ev_windows.c " +
+    "src\\core\\lib\\iomgr\\exec_ctx.c " +
+    "src\\core\\lib\\iomgr\\executor.c " +
+    "src\\core\\lib\\iomgr\\iocp_windows.c " +
+    "src\\core\\lib\\iomgr\\iomgr.c " +
+    "src\\core\\lib\\iomgr\\iomgr_posix.c " +
+    "src\\core\\lib\\iomgr\\iomgr_uv.c " +
+    "src\\core\\lib\\iomgr\\iomgr_windows.c " +
+    "src\\core\\lib\\iomgr\\is_epollexclusive_available.c " +
+    "src\\core\\lib\\iomgr\\load_file.c " +
+    "src\\core\\lib\\iomgr\\lockfree_event.c " +
+    "src\\core\\lib\\iomgr\\network_status_tracker.c " +
+    "src\\core\\lib\\iomgr\\polling_entity.c " +
+    "src\\core\\lib\\iomgr\\pollset_set_uv.c " +
+    "src\\core\\lib\\iomgr\\pollset_set_windows.c " +
+    "src\\core\\lib\\iomgr\\pollset_uv.c " +
+    "src\\core\\lib\\iomgr\\pollset_windows.c " +
+    "src\\core\\lib\\iomgr\\resolve_address_posix.c " +
+    "src\\core\\lib\\iomgr\\resolve_address_uv.c " +
+    "src\\core\\lib\\iomgr\\resolve_address_windows.c " +
+    "src\\core\\lib\\iomgr\\resource_quota.c " +
+    "src\\core\\lib\\iomgr\\sockaddr_utils.c " +
+    "src\\core\\lib\\iomgr\\socket_factory_posix.c " +
+    "src\\core\\lib\\iomgr\\socket_mutator.c " +
+    "src\\core\\lib\\iomgr\\socket_utils_common_posix.c " +
+    "src\\core\\lib\\iomgr\\socket_utils_linux.c " +
+    "src\\core\\lib\\iomgr\\socket_utils_posix.c " +
+    "src\\core\\lib\\iomgr\\socket_utils_uv.c " +
+    "src\\core\\lib\\iomgr\\socket_utils_windows.c " +
+    "src\\core\\lib\\iomgr\\socket_windows.c " +
+    "src\\core\\lib\\iomgr\\tcp_client_posix.c " +
+    "src\\core\\lib\\iomgr\\tcp_client_uv.c " +
+    "src\\core\\lib\\iomgr\\tcp_client_windows.c " +
+    "src\\core\\lib\\iomgr\\tcp_posix.c " +
+    "src\\core\\lib\\iomgr\\tcp_server_posix.c " +
+    "src\\core\\lib\\iomgr\\tcp_server_utils_posix_common.c " +
+    "src\\core\\lib\\iomgr\\tcp_server_utils_posix_ifaddrs.c " +
+    "src\\core\\lib\\iomgr\\tcp_server_utils_posix_noifaddrs.c " +
+    "src\\core\\lib\\iomgr\\tcp_server_uv.c " +
+    "src\\core\\lib\\iomgr\\tcp_server_windows.c " +
+    "src\\core\\lib\\iomgr\\tcp_uv.c " +
+    "src\\core\\lib\\iomgr\\tcp_windows.c " +
+    "src\\core\\lib\\iomgr\\time_averaged_stats.c " +
+    "src\\core\\lib\\iomgr\\timer_generic.c " +
+    "src\\core\\lib\\iomgr\\timer_heap.c " +
+    "src\\core\\lib\\iomgr\\timer_manager.c " +
+    "src\\core\\lib\\iomgr\\timer_uv.c " +
+    "src\\core\\lib\\iomgr\\udp_server.c " +
+    "src\\core\\lib\\iomgr\\unix_sockets_posix.c " +
+    "src\\core\\lib\\iomgr\\unix_sockets_posix_noop.c " +
+    "src\\core\\lib\\iomgr\\wakeup_fd_cv.c " +
+    "src\\core\\lib\\iomgr\\wakeup_fd_eventfd.c " +
+    "src\\core\\lib\\iomgr\\wakeup_fd_nospecial.c " +
+    "src\\core\\lib\\iomgr\\wakeup_fd_pipe.c " +
+    "src\\core\\lib\\iomgr\\wakeup_fd_posix.c " +
+    "src\\core\\lib\\iomgr\\workqueue_uv.c " +
+    "src\\core\\lib\\iomgr\\workqueue_windows.c " +
+    "src\\core\\lib\\json\\json.c " +
+    "src\\core\\lib\\json\\json_reader.c " +
+    "src\\core\\lib\\json\\json_string.c " +
+    "src\\core\\lib\\json\\json_writer.c " +
+    "src\\core\\lib\\slice\\b64.c " +
+    "src\\core\\lib\\slice\\percent_encoding.c " +
+    "src\\core\\lib\\slice\\slice.c " +
+    "src\\core\\lib\\slice\\slice_buffer.c " +
+    "src\\core\\lib\\slice\\slice_hash_table.c " +
+    "src\\core\\lib\\slice\\slice_intern.c " +
+    "src\\core\\lib\\slice\\slice_string_helpers.c " +
+    "src\\core\\lib\\surface\\alarm.c " +
+    "src\\core\\lib\\surface\\api_trace.c " +
+    "src\\core\\lib\\surface\\byte_buffer.c " +
+    "src\\core\\lib\\surface\\byte_buffer_reader.c " +
+    "src\\core\\lib\\surface\\call.c " +
+    "src\\core\\lib\\surface\\call_details.c " +
+    "src\\core\\lib\\surface\\call_log_batch.c " +
+    "src\\core\\lib\\surface\\channel.c " +
+    "src\\core\\lib\\surface\\channel_init.c " +
+    "src\\core\\lib\\surface\\channel_ping.c " +
+    "src\\core\\lib\\surface\\channel_stack_type.c " +
+    "src\\core\\lib\\surface\\completion_queue.c " +
+    "src\\core\\lib\\surface\\completion_queue_factory.c " +
+    "src\\core\\lib\\surface\\event_string.c " +
+    "src\\core\\lib\\surface\\lame_client.cc " +
+    "src\\core\\lib\\surface\\metadata_array.c " +
+    "src\\core\\lib\\surface\\server.c " +
+    "src\\core\\lib\\surface\\validate_metadata.c " +
+    "src\\core\\lib\\surface\\version.c " +
+    "src\\core\\lib\\transport\\bdp_estimator.c " +
+    "src\\core\\lib\\transport\\byte_stream.c " +
+    "src\\core\\lib\\transport\\connectivity_state.c " +
+    "src\\core\\lib\\transport\\error_utils.c " +
+    "src\\core\\lib\\transport\\metadata.c " +
+    "src\\core\\lib\\transport\\metadata_batch.c " +
+    "src\\core\\lib\\transport\\pid_controller.c " +
+    "src\\core\\lib\\transport\\service_config.c " +
+    "src\\core\\lib\\transport\\static_metadata.c " +
+    "src\\core\\lib\\transport\\status_conversion.c " +
+    "src\\core\\lib\\transport\\timeout_encoding.c " +
+    "src\\core\\lib\\transport\\transport.c " +
+    "src\\core\\lib\\transport\\transport_op_string.c " +
+    "src\\core\\lib\\debug\\trace.c " +
+    "src\\core\\ext\\transport\\chttp2\\server\\secure\\server_secure_chttp2.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\bin_decoder.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\bin_encoder.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\chttp2_plugin.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\chttp2_transport.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\frame_data.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\frame_goaway.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\frame_ping.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\frame_rst_stream.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\frame_settings.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\frame_window_update.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\hpack_encoder.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\hpack_parser.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\hpack_table.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\http2_settings.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\huffsyms.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\incoming_metadata.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\parsing.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\stream_lists.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\stream_map.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\varint.c " +
+    "src\\core\\ext\\transport\\chttp2\\transport\\writing.c " +
+    "src\\core\\ext\\transport\\chttp2\\alpn\\alpn.c " +
+    "src\\core\\ext\\filters\\http\\client\\http_client_filter.c " +
+    "src\\core\\ext\\filters\\http\\http_filters_plugin.c " +
+    "src\\core\\ext\\filters\\http\\message_compress\\message_compress_filter.c " +
+    "src\\core\\ext\\filters\\http\\server\\http_server_filter.c " +
+    "src\\core\\lib\\http\\httpcli_security_connector.c " +
+    "src\\core\\lib\\security\\context\\security_context.c " +
+    "src\\core\\lib\\security\\credentials\\composite\\composite_credentials.c " +
+    "src\\core\\lib\\security\\credentials\\credentials.c " +
+    "src\\core\\lib\\security\\credentials\\credentials_metadata.c " +
+    "src\\core\\lib\\security\\credentials\\fake\\fake_credentials.c " +
+    "src\\core\\lib\\security\\credentials\\google_default\\credentials_generic.c " +
+    "src\\core\\lib\\security\\credentials\\google_default\\google_default_credentials.c " +
+    "src\\core\\lib\\security\\credentials\\iam\\iam_credentials.c " +
+    "src\\core\\lib\\security\\credentials\\jwt\\json_token.c " +
+    "src\\core\\lib\\security\\credentials\\jwt\\jwt_credentials.c " +
+    "src\\core\\lib\\security\\credentials\\jwt\\jwt_verifier.c " +
+    "src\\core\\lib\\security\\credentials\\oauth2\\oauth2_credentials.c " +
+    "src\\core\\lib\\security\\credentials\\plugin\\plugin_credentials.c " +
+    "src\\core\\lib\\security\\credentials\\ssl\\ssl_credentials.c " +
+    "src\\core\\lib\\security\\transport\\client_auth_filter.c " +
+    "src\\core\\lib\\security\\transport\\lb_targets_info.c " +
+    "src\\core\\lib\\security\\transport\\secure_endpoint.c " +
+    "src\\core\\lib\\security\\transport\\security_connector.c " +
+    "src\\core\\lib\\security\\transport\\security_handshaker.c " +
+    "src\\core\\lib\\security\\transport\\server_auth_filter.c " +
+    "src\\core\\lib\\security\\transport\\tsi_error.c " +
+    "src\\core\\lib\\security\\util\\json_util.c " +
+    "src\\core\\lib\\surface\\init_secure.c " +
+    "src\\core\\tsi\\fake_transport_security.c " +
+    "src\\core\\tsi\\ssl_transport_security.c " +
+    "src\\core\\tsi\\transport_security.c " +
+    "src\\core\\tsi\\transport_security_adapter.c " +
+    "src\\core\\ext\\transport\\chttp2\\server\\chttp2_server.c " +
+    "src\\core\\ext\\transport\\chttp2\\client\\secure\\secure_channel_create.c " +
+    "src\\core\\ext\\filters\\client_channel\\channel_connectivity.c " +
+    "src\\core\\ext\\filters\\client_channel\\client_channel.c " +
+    "src\\core\\ext\\filters\\client_channel\\client_channel_factory.c " +
+    "src\\core\\ext\\filters\\client_channel\\client_channel_plugin.c " +
+    "src\\core\\ext\\filters\\client_channel\\connector.c " +
+    "src\\core\\ext\\filters\\client_channel\\http_connect_handshaker.c " +
+    "src\\core\\ext\\filters\\client_channel\\http_proxy.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy_factory.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy_registry.c " +
+    "src\\core\\ext\\filters\\client_channel\\parse_address.c " +
+    "src\\core\\ext\\filters\\client_channel\\proxy_mapper.c " +
+    "src\\core\\ext\\filters\\client_channel\\proxy_mapper_registry.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver_factory.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver_registry.c " +
+    "src\\core\\ext\\filters\\client_channel\\retry_throttle.c " +
+    "src\\core\\ext\\filters\\client_channel\\subchannel.c " +
+    "src\\core\\ext\\filters\\client_channel\\subchannel_index.c " +
+    "src\\core\\ext\\filters\\client_channel\\uri_parser.c " +
+    "src\\core\\ext\\filters\\deadline\\deadline_filter.c " +
+    "src\\core\\ext\\transport\\chttp2\\client\\chttp2_connector.c " +
+    "src\\core\\ext\\transport\\chttp2\\server\\insecure\\server_chttp2.c " +
+    "src\\core\\ext\\transport\\chttp2\\server\\insecure\\server_chttp2_posix.c " +
+    "src\\core\\ext\\transport\\chttp2\\client\\insecure\\channel_create.c " +
+    "src\\core\\ext\\transport\\chttp2\\client\\insecure\\channel_create_posix.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\client_load_reporting_filter.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\grpclb.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\grpclb_channel_secure.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\grpclb_client_stats.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\load_balancer_api.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\proto\\grpc\\lb\\v1\\load_balancer.pb.c " +
+    "third_party\\nanopb\\pb_common.c " +
+    "third_party\\nanopb\\pb_decode.c " +
+    "third_party\\nanopb\\pb_encode.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\pick_first\\pick_first.c " +
+    "src\\core\\ext\\filters\\client_channel\\lb_policy\\round_robin\\round_robin.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\dns_resolver_ares.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_ev_driver_posix.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\native\\dns_resolver.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\sockaddr\\sockaddr_resolver.c " +
+    "src\\core\\ext\\filters\\load_reporting\\load_reporting.c " +
+    "src\\core\\ext\\filters\\load_reporting\\load_reporting_filter.c " +
+    "src\\core\\ext\\census\\base_resources.c " +
+    "src\\core\\ext\\census\\context.c " +
+    "src\\core\\ext\\census\\gen\\census.pb.c " +
+    "src\\core\\ext\\census\\gen\\trace_context.pb.c " +
+    "src\\core\\ext\\census\\grpc_context.c " +
+    "src\\core\\ext\\census\\grpc_filter.c " +
+    "src\\core\\ext\\census\\grpc_plugin.c " +
+    "src\\core\\ext\\census\\initialize.c " +
+    "src\\core\\ext\\census\\intrusive_hash_map.c " +
+    "src\\core\\ext\\census\\mlog.c " +
+    "src\\core\\ext\\census\\operation.c " +
+    "src\\core\\ext\\census\\placeholders.c " +
+    "src\\core\\ext\\census\\resource.c " +
+    "src\\core\\ext\\census\\trace_context.c " +
+    "src\\core\\ext\\census\\tracing.c " +
+    "src\\core\\ext\\filters\\max_age\\max_age_filter.c " +
+    "src\\core\\ext\\filters\\message_size\\message_size_filter.c " +
+    "src\\core\\ext\\filters\\workarounds\\workaround_cronet_compression_filter.c " +
+    "src\\core\\ext\\filters\\workarounds\\workaround_utils.c " +
+    "src\\core\\plugin_registry\\grpc_plugin_registry.c " +
+    "src\\boringssl\\err_data.c " +
+    "third_party\\boringssl\\crypto\\aes\\aes.c " +
+    "third_party\\boringssl\\crypto\\aes\\mode_wrappers.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_bitstr.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_bool.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_d2i_fp.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_dup.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_enum.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_gentm.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_i2d_fp.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_int.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_mbstr.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_object.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_octet.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_print.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_strnid.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_time.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_type.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_utctm.c " +
+    "third_party\\boringssl\\crypto\\asn1\\a_utf8.c " +
+    "third_party\\boringssl\\crypto\\asn1\\asn1_lib.c " +
+    "third_party\\boringssl\\crypto\\asn1\\asn1_par.c " +
+    "third_party\\boringssl\\crypto\\asn1\\asn_pack.c " +
+    "third_party\\boringssl\\crypto\\asn1\\f_enum.c " +
+    "third_party\\boringssl\\crypto\\asn1\\f_int.c " +
+    "third_party\\boringssl\\crypto\\asn1\\f_string.c " +
+    "third_party\\boringssl\\crypto\\asn1\\t_bitst.c " +
+    "third_party\\boringssl\\crypto\\asn1\\tasn_dec.c " +
+    "third_party\\boringssl\\crypto\\asn1\\tasn_enc.c " +
+    "third_party\\boringssl\\crypto\\asn1\\tasn_fre.c " +
+    "third_party\\boringssl\\crypto\\asn1\\tasn_new.c " +
+    "third_party\\boringssl\\crypto\\asn1\\tasn_typ.c " +
+    "third_party\\boringssl\\crypto\\asn1\\tasn_utl.c " +
+    "third_party\\boringssl\\crypto\\asn1\\x_bignum.c " +
+    "third_party\\boringssl\\crypto\\asn1\\x_long.c " +
+    "third_party\\boringssl\\crypto\\base64\\base64.c " +
+    "third_party\\boringssl\\crypto\\bio\\bio.c " +
+    "third_party\\boringssl\\crypto\\bio\\bio_mem.c " +
+    "third_party\\boringssl\\crypto\\bio\\buffer.c " +
+    "third_party\\boringssl\\crypto\\bio\\connect.c " +
+    "third_party\\boringssl\\crypto\\bio\\fd.c " +
+    "third_party\\boringssl\\crypto\\bio\\file.c " +
+    "third_party\\boringssl\\crypto\\bio\\hexdump.c " +
+    "third_party\\boringssl\\crypto\\bio\\pair.c " +
+    "third_party\\boringssl\\crypto\\bio\\printf.c " +
+    "third_party\\boringssl\\crypto\\bio\\socket.c " +
+    "third_party\\boringssl\\crypto\\bio\\socket_helper.c " +
+    "third_party\\boringssl\\crypto\\bn\\add.c " +
+    "third_party\\boringssl\\crypto\\bn\\asm\\x86_64-gcc.c " +
+    "third_party\\boringssl\\crypto\\bn\\bn.c " +
+    "third_party\\boringssl\\crypto\\bn\\bn_asn1.c " +
+    "third_party\\boringssl\\crypto\\bn\\cmp.c " +
+    "third_party\\boringssl\\crypto\\bn\\convert.c " +
+    "third_party\\boringssl\\crypto\\bn\\ctx.c " +
+    "third_party\\boringssl\\crypto\\bn\\div.c " +
+    "third_party\\boringssl\\crypto\\bn\\exponentiation.c " +
+    "third_party\\boringssl\\crypto\\bn\\gcd.c " +
+    "third_party\\boringssl\\crypto\\bn\\generic.c " +
+    "third_party\\boringssl\\crypto\\bn\\kronecker.c " +
+    "third_party\\boringssl\\crypto\\bn\\montgomery.c " +
+    "third_party\\boringssl\\crypto\\bn\\montgomery_inv.c " +
+    "third_party\\boringssl\\crypto\\bn\\mul.c " +
+    "third_party\\boringssl\\crypto\\bn\\prime.c " +
+    "third_party\\boringssl\\crypto\\bn\\random.c " +
+    "third_party\\boringssl\\crypto\\bn\\rsaz_exp.c " +
+    "third_party\\boringssl\\crypto\\bn\\shift.c " +
+    "third_party\\boringssl\\crypto\\bn\\sqrt.c " +
+    "third_party\\boringssl\\crypto\\buf\\buf.c " +
+    "third_party\\boringssl\\crypto\\bytestring\\asn1_compat.c " +
+    "third_party\\boringssl\\crypto\\bytestring\\ber.c " +
+    "third_party\\boringssl\\crypto\\bytestring\\cbb.c " +
+    "third_party\\boringssl\\crypto\\bytestring\\cbs.c " +
+    "third_party\\boringssl\\crypto\\chacha\\chacha.c " +
+    "third_party\\boringssl\\crypto\\cipher\\aead.c " +
+    "third_party\\boringssl\\crypto\\cipher\\cipher.c " +
+    "third_party\\boringssl\\crypto\\cipher\\derive_key.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_aes.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_chacha20poly1305.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_des.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_null.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_rc2.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_rc4.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_ssl3.c " +
+    "third_party\\boringssl\\crypto\\cipher\\e_tls.c " +
+    "third_party\\boringssl\\crypto\\cipher\\tls_cbc.c " +
+    "third_party\\boringssl\\crypto\\cmac\\cmac.c " +
+    "third_party\\boringssl\\crypto\\conf\\conf.c " +
+    "third_party\\boringssl\\crypto\\cpu-aarch64-linux.c " +
+    "third_party\\boringssl\\crypto\\cpu-arm-linux.c " +
+    "third_party\\boringssl\\crypto\\cpu-arm.c " +
+    "third_party\\boringssl\\crypto\\cpu-intel.c " +
+    "third_party\\boringssl\\crypto\\cpu-ppc64le.c " +
+    "third_party\\boringssl\\crypto\\crypto.c " +
+    "third_party\\boringssl\\crypto\\curve25519\\curve25519.c " +
+    "third_party\\boringssl\\crypto\\curve25519\\spake25519.c " +
+    "third_party\\boringssl\\crypto\\curve25519\\x25519-x86_64.c " +
+    "third_party\\boringssl\\crypto\\des\\des.c " +
+    "third_party\\boringssl\\crypto\\dh\\check.c " +
+    "third_party\\boringssl\\crypto\\dh\\dh.c " +
+    "third_party\\boringssl\\crypto\\dh\\dh_asn1.c " +
+    "third_party\\boringssl\\crypto\\dh\\params.c " +
+    "third_party\\boringssl\\crypto\\digest\\digest.c " +
+    "third_party\\boringssl\\crypto\\digest\\digests.c " +
+    "third_party\\boringssl\\crypto\\dsa\\dsa.c " +
+    "third_party\\boringssl\\crypto\\dsa\\dsa_asn1.c " +
+    "third_party\\boringssl\\crypto\\ec\\ec.c " +
+    "third_party\\boringssl\\crypto\\ec\\ec_asn1.c " +
+    "third_party\\boringssl\\crypto\\ec\\ec_key.c " +
+    "third_party\\boringssl\\crypto\\ec\\ec_montgomery.c " +
+    "third_party\\boringssl\\crypto\\ec\\oct.c " +
+    "third_party\\boringssl\\crypto\\ec\\p224-64.c " +
+    "third_party\\boringssl\\crypto\\ec\\p256-64.c " +
+    "third_party\\boringssl\\crypto\\ec\\p256-x86_64.c " +
+    "third_party\\boringssl\\crypto\\ec\\simple.c " +
+    "third_party\\boringssl\\crypto\\ec\\util-64.c " +
+    "third_party\\boringssl\\crypto\\ec\\wnaf.c " +
+    "third_party\\boringssl\\crypto\\ecdh\\ecdh.c " +
+    "third_party\\boringssl\\crypto\\ecdsa\\ecdsa.c " +
+    "third_party\\boringssl\\crypto\\ecdsa\\ecdsa_asn1.c " +
+    "third_party\\boringssl\\crypto\\engine\\engine.c " +
+    "third_party\\boringssl\\crypto\\err\\err.c " +
+    "third_party\\boringssl\\crypto\\evp\\digestsign.c " +
+    "third_party\\boringssl\\crypto\\evp\\evp.c " +
+    "third_party\\boringssl\\crypto\\evp\\evp_asn1.c " +
+    "third_party\\boringssl\\crypto\\evp\\evp_ctx.c " +
+    "third_party\\boringssl\\crypto\\evp\\p_dsa_asn1.c " +
+    "third_party\\boringssl\\crypto\\evp\\p_ec.c " +
+    "third_party\\boringssl\\crypto\\evp\\p_ec_asn1.c " +
+    "third_party\\boringssl\\crypto\\evp\\p_rsa.c " +
+    "third_party\\boringssl\\crypto\\evp\\p_rsa_asn1.c " +
+    "third_party\\boringssl\\crypto\\evp\\pbkdf.c " +
+    "third_party\\boringssl\\crypto\\evp\\print.c " +
+    "third_party\\boringssl\\crypto\\evp\\sign.c " +
+    "third_party\\boringssl\\crypto\\ex_data.c " +
+    "third_party\\boringssl\\crypto\\hkdf\\hkdf.c " +
+    "third_party\\boringssl\\crypto\\hmac\\hmac.c " +
+    "third_party\\boringssl\\crypto\\lhash\\lhash.c " +
+    "third_party\\boringssl\\crypto\\md4\\md4.c " +
+    "third_party\\boringssl\\crypto\\md5\\md5.c " +
+    "third_party\\boringssl\\crypto\\mem.c " +
+    "third_party\\boringssl\\crypto\\modes\\cbc.c " +
+    "third_party\\boringssl\\crypto\\modes\\cfb.c " +
+    "third_party\\boringssl\\crypto\\modes\\ctr.c " +
+    "third_party\\boringssl\\crypto\\modes\\gcm.c " +
+    "third_party\\boringssl\\crypto\\modes\\ofb.c " +
+    "third_party\\boringssl\\crypto\\newhope\\error_correction.c " +
+    "third_party\\boringssl\\crypto\\newhope\\newhope.c " +
+    "third_party\\boringssl\\crypto\\newhope\\ntt.c " +
+    "third_party\\boringssl\\crypto\\newhope\\poly.c " +
+    "third_party\\boringssl\\crypto\\newhope\\precomp.c " +
+    "third_party\\boringssl\\crypto\\newhope\\reduce.c " +
+    "third_party\\boringssl\\crypto\\obj\\obj.c " +
+    "third_party\\boringssl\\crypto\\obj\\obj_xref.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_all.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_info.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_lib.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_oth.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_pk8.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_pkey.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_x509.c " +
+    "third_party\\boringssl\\crypto\\pem\\pem_xaux.c " +
+    "third_party\\boringssl\\crypto\\pkcs8\\p5_pbe.c " +
+    "third_party\\boringssl\\crypto\\pkcs8\\p5_pbev2.c " +
+    "third_party\\boringssl\\crypto\\pkcs8\\p8_pkey.c " +
+    "third_party\\boringssl\\crypto\\pkcs8\\pkcs8.c " +
+    "third_party\\boringssl\\crypto\\poly1305\\poly1305.c " +
+    "third_party\\boringssl\\crypto\\poly1305\\poly1305_arm.c " +
+    "third_party\\boringssl\\crypto\\poly1305\\poly1305_vec.c " +
+    "third_party\\boringssl\\crypto\\rand\\deterministic.c " +
+    "third_party\\boringssl\\crypto\\rand\\rand.c " +
+    "third_party\\boringssl\\crypto\\rand\\urandom.c " +
+    "third_party\\boringssl\\crypto\\rand\\windows.c " +
+    "third_party\\boringssl\\crypto\\rc4\\rc4.c " +
+    "third_party\\boringssl\\crypto\\refcount_c11.c " +
+    "third_party\\boringssl\\crypto\\refcount_lock.c " +
+    "third_party\\boringssl\\crypto\\rsa\\blinding.c " +
+    "third_party\\boringssl\\crypto\\rsa\\padding.c " +
+    "third_party\\boringssl\\crypto\\rsa\\rsa.c " +
+    "third_party\\boringssl\\crypto\\rsa\\rsa_asn1.c " +
+    "third_party\\boringssl\\crypto\\rsa\\rsa_impl.c " +
+    "third_party\\boringssl\\crypto\\sha\\sha1.c " +
+    "third_party\\boringssl\\crypto\\sha\\sha256.c " +
+    "third_party\\boringssl\\crypto\\sha\\sha512.c " +
+    "third_party\\boringssl\\crypto\\stack\\stack.c " +
+    "third_party\\boringssl\\crypto\\thread.c " +
+    "third_party\\boringssl\\crypto\\thread_none.c " +
+    "third_party\\boringssl\\crypto\\thread_pthread.c " +
+    "third_party\\boringssl\\crypto\\thread_win.c " +
+    "third_party\\boringssl\\crypto\\time_support.c " +
+    "third_party\\boringssl\\crypto\\x509\\a_digest.c " +
+    "third_party\\boringssl\\crypto\\x509\\a_sign.c " +
+    "third_party\\boringssl\\crypto\\x509\\a_strex.c " +
+    "third_party\\boringssl\\crypto\\x509\\a_verify.c " +
+    "third_party\\boringssl\\crypto\\x509\\algorithm.c " +
+    "third_party\\boringssl\\crypto\\x509\\asn1_gen.c " +
+    "third_party\\boringssl\\crypto\\x509\\by_dir.c " +
+    "third_party\\boringssl\\crypto\\x509\\by_file.c " +
+    "third_party\\boringssl\\crypto\\x509\\i2d_pr.c " +
+    "third_party\\boringssl\\crypto\\x509\\pkcs7.c " +
+    "third_party\\boringssl\\crypto\\x509\\rsa_pss.c " +
+    "third_party\\boringssl\\crypto\\x509\\t_crl.c " +
+    "third_party\\boringssl\\crypto\\x509\\t_req.c " +
+    "third_party\\boringssl\\crypto\\x509\\t_x509.c " +
+    "third_party\\boringssl\\crypto\\x509\\t_x509a.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_att.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_cmp.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_d2.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_def.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_ext.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_lu.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_obj.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_r2x.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_req.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_set.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_trs.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_txt.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_v3.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_vfy.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509_vpm.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509cset.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509name.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509rset.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509spki.c " +
+    "third_party\\boringssl\\crypto\\x509\\x509type.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_algor.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_all.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_attrib.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_crl.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_exten.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_info.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_name.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_pkey.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_pubkey.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_req.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_sig.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_spki.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_val.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_x509.c " +
+    "third_party\\boringssl\\crypto\\x509\\x_x509a.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\pcy_cache.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\pcy_data.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\pcy_lib.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\pcy_map.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\pcy_node.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\pcy_tree.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_akey.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_akeya.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_alt.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_bcons.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_bitst.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_conf.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_cpols.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_crld.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_enum.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_extku.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_genn.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_ia5.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_info.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_int.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_lib.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_ncons.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_pci.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_pcia.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_pcons.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_pku.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_pmaps.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_prn.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_purp.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_skey.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_sxnet.c " +
+    "third_party\\boringssl\\crypto\\x509v3\\v3_utl.c " +
+    "third_party\\boringssl\\ssl\\custom_extensions.c " +
+    "third_party\\boringssl\\ssl\\d1_both.c " +
+    "third_party\\boringssl\\ssl\\d1_lib.c " +
+    "third_party\\boringssl\\ssl\\d1_pkt.c " +
+    "third_party\\boringssl\\ssl\\d1_srtp.c " +
+    "third_party\\boringssl\\ssl\\dtls_method.c " +
+    "third_party\\boringssl\\ssl\\dtls_record.c " +
+    "third_party\\boringssl\\ssl\\handshake_client.c " +
+    "third_party\\boringssl\\ssl\\handshake_server.c " +
+    "third_party\\boringssl\\ssl\\s3_both.c " +
+    "third_party\\boringssl\\ssl\\s3_enc.c " +
+    "third_party\\boringssl\\ssl\\s3_lib.c " +
+    "third_party\\boringssl\\ssl\\s3_pkt.c " +
+    "third_party\\boringssl\\ssl\\ssl_aead_ctx.c " +
+    "third_party\\boringssl\\ssl\\ssl_asn1.c " +
+    "third_party\\boringssl\\ssl\\ssl_buffer.c " +
+    "third_party\\boringssl\\ssl\\ssl_cert.c " +
+    "third_party\\boringssl\\ssl\\ssl_cipher.c " +
+    "third_party\\boringssl\\ssl\\ssl_ecdh.c " +
+    "third_party\\boringssl\\ssl\\ssl_file.c " +
+    "third_party\\boringssl\\ssl\\ssl_lib.c " +
+    "third_party\\boringssl\\ssl\\ssl_rsa.c " +
+    "third_party\\boringssl\\ssl\\ssl_session.c " +
+    "third_party\\boringssl\\ssl\\ssl_stat.c " +
+    "third_party\\boringssl\\ssl\\t1_enc.c " +
+    "third_party\\boringssl\\ssl\\t1_lib.c " +
+    "third_party\\boringssl\\ssl\\tls13_both.c " +
+    "third_party\\boringssl\\ssl\\tls13_client.c " +
+    "third_party\\boringssl\\ssl\\tls13_enc.c " +
+    "third_party\\boringssl\\ssl\\tls13_server.c " +
+    "third_party\\boringssl\\ssl\\tls_method.c " +
+    "third_party\\boringssl\\ssl\\tls_record.c " +
+    "third_party\\zlib\\adler32.c " +
+    "third_party\\zlib\\compress.c " +
+    "third_party\\zlib\\crc32.c " +
+    "third_party\\zlib\\deflate.c " +
+    "third_party\\zlib\\gzclose.c " +
+    "third_party\\zlib\\gzlib.c " +
+    "third_party\\zlib\\gzread.c " +
+    "third_party\\zlib\\gzwrite.c " +
+    "third_party\\zlib\\infback.c " +
+    "third_party\\zlib\\inffast.c " +
+    "third_party\\zlib\\inflate.c " +
+    "third_party\\zlib\\inftrees.c " +
+    "third_party\\zlib\\trees.c " +
+    "third_party\\zlib\\uncompr.c " +
+    "third_party\\zlib\\zutil.c " +
+    "";
+
+  EXTENSION("grpc", grpc_source, null,
+    "/DOPENSSL_NO_ASM /D_GNU_SOURCE /DWIN32_LEAN_AND_MEAN "+
+    "/D_HAS_EXCEPTIONS=0 /DNOMINMAX /DGRPC_ARES=0 /D_WIN32_WINNT=0x600 "+
+    "/I"+configure_module_dirname+" "+
+    "/I"+configure_module_dirname+"\\include "+
+    "/I"+configure_module_dirname+"\\src\\php\\ext\\grpc "+
+    "/I"+configure_module_dirname+"\\third_party\\boringssl\\include "+
+    "/I"+configure_module_dirname+"\\third_party\\zlib");
+
+  base_dir = get_define('BUILD_DIR');
+  FSO.CreateFolder(base_dir+"\\ext");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\boringssl");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\census");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\census\\gen");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\proto");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\proto\\grpc");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\proto\\grpc\\lb");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\proto\\grpc\\lb\\v1");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\pick_first");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\round_robin");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\resolver");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\resolver\\dns");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\resolver\\dns\\native");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\client_channel\\resolver\\sockaddr");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\deadline");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\http");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\http\\client");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\http\\message_compress");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\http\\server");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\load_reporting");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\max_age");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\message_size");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\workarounds");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\alpn");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\client");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\client\\insecure");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\client\\secure");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\server");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\server\\insecure");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\server\\secure");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\transport\\chttp2\\transport");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\channel");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\compression");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\debug");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\http");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\iomgr");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\json");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\profiling");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\context");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\composite");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\fake");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\google_default");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\iam");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\jwt");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\oauth2");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\plugin");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\ssl");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\transport");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\util");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\slice");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\support");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\surface");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\transport");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\plugin_registry");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php\\ext");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php\\ext\\grpc");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\aes");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\asn1");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\base64");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\bio");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\bn");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\bn\\asm");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\buf");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\bytestring");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\chacha");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\cipher");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\cmac");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\conf");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\curve25519");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\des");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\dh");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\digest");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\dsa");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\ec");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\ecdh");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\ecdsa");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\engine");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\err");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\evp");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\hkdf");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\hmac");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\lhash");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\md4");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\md5");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\modes");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\newhope");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\obj");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\pem");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\pkcs8");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\poly1305");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\rand");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\rc4");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\rsa");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\sha");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\stack");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\x509");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\crypto\\x509v3");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\boringssl\\ssl");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\nanopb");
+  FSO.CreateFolder(base_dir+"\\ext\\grpc\\third_party\\zlib");
+  _build_dirs = new Array();
+  for (i = 0; i < build_dirs.length; i++) {
+    if (build_dirs[i].indexOf('grpc') == -1) {
+      _build_dirs[_build_dirs.length] = build_dirs[i];
+    }
+  }
+  build_dirs = _build_dirs;
+
+}
diff --git a/doc/PROTOCOL-WEB.md b/doc/PROTOCOL-WEB.md
index 1099889..912dbe6 100644
--- a/doc/PROTOCOL-WEB.md
+++ b/doc/PROTOCOL-WEB.md
@@ -103,10 +103,6 @@
   * While the server runtime will always base64-encode and flush gRPC messages
   atomically the client library should not assume base64 padding always
   happens at the boundary of message frames. That is, the implementation may send base64-encoded "chunks" with potential padding whenever the runtime needs to flush a byte buffer.
-3. For binary trailers, when the content-type is set to
-application/grpc-web-text, the extra base64 encoding specified
-in [gRPC over HTTP2](http://www.grpc.io/docs/guides/wire.html)
-for binary custom metadata is skipped.
 
 # Other features
 
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 1176a15..44e8931 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -37,7 +37,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-Core'
-  version = '1.4.0-dev'
+  version = '1.4.0-pre1'
   s.version  = version
   s.summary  = 'Core cross-platform gRPC library, written in C'
   s.homepage = 'http://www.grpc.io'
@@ -463,6 +463,8 @@
                       'src/core/ext/census/gen/census.pb.h',
                       'src/core/ext/census/gen/trace_context.pb.h',
                       'src/core/ext/census/grpc_filter.h',
+                      'src/core/ext/census/intrusive_hash_map.h',
+                      'src/core/ext/census/intrusive_hash_map_internal.h',
                       'src/core/ext/census/mlog.h',
                       'src/core/ext/census/resource.h',
                       'src/core/ext/census/rpc_metric_id.h',
@@ -713,6 +715,7 @@
                       'src/core/ext/census/grpc_filter.c',
                       'src/core/ext/census/grpc_plugin.c',
                       'src/core/ext/census/initialize.c',
+                      'src/core/ext/census/intrusive_hash_map.c',
                       'src/core/ext/census/mlog.c',
                       'src/core/ext/census/operation.c',
                       'src/core/ext/census/placeholders.c',
@@ -946,6 +949,8 @@
                               'src/core/ext/census/gen/census.pb.h',
                               'src/core/ext/census/gen/trace_context.pb.h',
                               'src/core/ext/census/grpc_filter.h',
+                              'src/core/ext/census/intrusive_hash_map.h',
+                              'src/core/ext/census/intrusive_hash_map_internal.h',
                               'src/core/ext/census/mlog.h',
                               'src/core/ext/census/resource.h',
                               'src/core/ext/census/rpc_metric_id.h',
diff --git a/gRPC-ProtoRPC.podspec b/gRPC-ProtoRPC.podspec
index 62cb0d1..88a64b8 100644
--- a/gRPC-ProtoRPC.podspec
+++ b/gRPC-ProtoRPC.podspec
@@ -36,7 +36,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-ProtoRPC'
-  version = '1.4.0-dev'
+  version = '1.4.0-pre1'
   s.version  = version
   s.summary  = 'RPC library for Protocol Buffers, based on gRPC'
   s.homepage = 'http://www.grpc.io'
diff --git a/gRPC-RxLibrary.podspec b/gRPC-RxLibrary.podspec
index 77ceb22..cc6ab6c 100644
--- a/gRPC-RxLibrary.podspec
+++ b/gRPC-RxLibrary.podspec
@@ -36,7 +36,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-RxLibrary'
-  version = '1.4.0-dev'
+  version = '1.4.0-pre1'
   s.version  = version
   s.summary  = 'Reactive Extensions library for iOS/OSX.'
   s.homepage = 'http://www.grpc.io'
diff --git a/gRPC.podspec b/gRPC.podspec
index 10520bd..cf63bfe 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -35,7 +35,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC'
-  version = '1.4.0-dev'
+  version = '1.4.0-pre1'
   s.version  = version
   s.summary  = 'gRPC client library for iOS/OSX'
   s.homepage = 'http://www.grpc.io'
diff --git a/grpc.def b/grpc.def
index 293f2d8..af4bd16 100644
--- a/grpc.def
+++ b/grpc.def
@@ -65,6 +65,7 @@
     grpc_alarm_cancel
     grpc_alarm_destroy
     grpc_channel_check_connectivity_state
+    grpc_channel_num_external_connectivity_watchers
     grpc_channel_watch_connectivity_state
     grpc_channel_create_call
     grpc_channel_ping
@@ -232,6 +233,7 @@
     gpr_histogram_merge_contents
     gpr_join_host_port
     gpr_split_host_port
+    gpr_log_severity_string
     gpr_log
     gpr_log_message
     gpr_set_log_verbosity
diff --git a/grpc.gemspec b/grpc.gemspec
index 32c1164..b334efb 100755
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -379,6 +379,8 @@
   s.files += %w( src/core/ext/census/gen/census.pb.h )
   s.files += %w( src/core/ext/census/gen/trace_context.pb.h )
   s.files += %w( src/core/ext/census/grpc_filter.h )
+  s.files += %w( src/core/ext/census/intrusive_hash_map.h )
+  s.files += %w( src/core/ext/census/intrusive_hash_map_internal.h )
   s.files += %w( src/core/ext/census/mlog.h )
   s.files += %w( src/core/ext/census/resource.h )
   s.files += %w( src/core/ext/census/rpc_metric_id.h )
@@ -629,6 +631,7 @@
   s.files += %w( src/core/ext/census/grpc_filter.c )
   s.files += %w( src/core/ext/census/grpc_plugin.c )
   s.files += %w( src/core/ext/census/initialize.c )
+  s.files += %w( src/core/ext/census/intrusive_hash_map.c )
   s.files += %w( src/core/ext/census/mlog.c )
   s.files += %w( src/core/ext/census/operation.c )
   s.files += %w( src/core/ext/census/placeholders.c )
diff --git a/include/grpc++/create_channel.h b/include/grpc++/create_channel.h
index 0537695..11f9bc0 100644
--- a/include/grpc++/create_channel.h
+++ b/include/grpc++/create_channel.h
@@ -43,23 +43,25 @@
 
 namespace grpc {
 
-/// Create a new \a Channel pointing to \a target
+/// Create a new \a Channel pointing to \a target.
 ///
 /// \param target The URI of the endpoint to connect to.
-/// \param creds Credentials to use for the created channel. If it does not hold
-/// an object or is invalid, a lame channel is returned.
+/// \param creds Credentials to use for the created channel. If it does not
+/// hold an object or is invalid, a lame channel (one on which all operations
+/// fail) is returned.
 std::shared_ptr<Channel> CreateChannel(
     const grpc::string& target,
     const std::shared_ptr<ChannelCredentials>& creds);
 
-/// Create a new \em custom \a Channel pointing to \a target
+/// Create a new \em custom \a Channel pointing to \a target.
 ///
 /// \warning For advanced use and testing ONLY. Override default channel
 /// arguments only if necessary.
 ///
 /// \param target The URI of the endpoint to connect to.
-/// \param creds Credentials to use for the created channel. If it does not hold
-/// an object or is invalid, a lame channel is returned.
+/// \param creds Credentials to use for the created channel. If it does not
+/// hold an object or is invalid, a lame channel (one on which all operations
+/// fail) is returned.
 /// \param args Options for channel creation.
 std::shared_ptr<Channel> CreateCustomChannel(
     const grpc::string& target,
diff --git a/include/grpc++/create_channel_posix.h b/include/grpc++/create_channel_posix.h
index 2af12e6..cb323a2 100644
--- a/include/grpc++/create_channel_posix.h
+++ b/include/grpc++/create_channel_posix.h
@@ -44,7 +44,7 @@
 
 #ifdef GPR_SUPPORT_CHANNELS_FROM_FD
 
-/// Create a new \a Channel communicating over given file descriptor
+/// Create a new \a Channel communicating over the given file descriptor.
 ///
 /// \param target The name of the target.
 /// \param fd The file descriptor representing a socket.
@@ -52,7 +52,7 @@
                                                      int fd);
 
 /// Create a new \a Channel communicating over given file descriptor with custom
-/// channel arguments
+/// channel arguments.
 ///
 /// \param target The name of the target.
 /// \param fd The file descriptor representing a socket.
diff --git a/include/grpc++/ext/health_check_service_server_builder_option.h b/include/grpc++/ext/health_check_service_server_builder_option.h
index a27841a..9da8e91 100644
--- a/include/grpc++/ext/health_check_service_server_builder_option.h
+++ b/include/grpc++/ext/health_check_service_server_builder_option.h
@@ -44,7 +44,7 @@
 
 class HealthCheckServiceServerBuilderOption : public ServerBuilderOption {
  public:
-  /// The ownership of hc will be taken and transferred to the grpc server.
+  /// The ownership of \a hc will be taken and transferred to the grpc server.
   /// To explicitly disable default service, pass in a nullptr.
   explicit HealthCheckServiceServerBuilderOption(
       std::unique_ptr<HealthCheckServiceInterface> hc);
diff --git a/include/grpc++/ext/proto_server_reflection_plugin.h b/include/grpc++/ext/proto_server_reflection_plugin.h
index 6497bba..f4a0eec 100644
--- a/include/grpc++/ext/proto_server_reflection_plugin.h
+++ b/include/grpc++/ext/proto_server_reflection_plugin.h
@@ -59,8 +59,8 @@
   std::shared_ptr<grpc::ProtoServerReflection> reflection_service_;
 };
 
-/// Add proto reflection plugin to ServerBuilder. This function should be called
-/// at the static initialization time.
+/// Add proto reflection plugin to \a ServerBuilder.
+/// This function should be called at the static initialization time.
 void InitProtoReflectionServerBuilderPlugin();
 
 }  // namespace reflection
diff --git a/include/grpc++/grpc++.h b/include/grpc++/grpc++.h
index 978b172..d6c3e2e 100644
--- a/include/grpc++/grpc++.h
+++ b/include/grpc++/grpc++.h
@@ -76,6 +76,7 @@
 // IWYU pragma: end_exports
 
 namespace grpc {
+/// Return gRPC library version.
 grpc::string Version();
 }  // namespace grpc
 
diff --git a/include/grpc++/health_check_service_interface.h b/include/grpc++/health_check_service_interface.h
index c1b4319..bdc4ed7 100644
--- a/include/grpc++/health_check_service_interface.h
+++ b/include/grpc++/health_check_service_interface.h
@@ -47,7 +47,7 @@
  public:
   virtual ~HealthCheckServiceInterface() {}
 
-  /// Set or change the serving status of the given service_name.
+  /// Set or change the serving status of the given \a service_name.
   virtual void SetServingStatus(const grpc::string& service_name,
                                 bool serving) = 0;
   /// Apply to all registered service names.
diff --git a/include/grpc++/impl/codegen/client_context.h b/include/grpc++/impl/codegen/client_context.h
index b1b9be0..d216cc5 100644
--- a/include/grpc++/impl/codegen/client_context.h
+++ b/include/grpc++/impl/codegen/client_context.h
@@ -307,7 +307,7 @@
 
   /// Flag whether the initial metadata should be \a corked
   ///
-  /// If \a corked is true, then the initial metadata will be colasced with the
+  /// If \a corked is true, then the initial metadata will be coalesced with the
   /// write of first message in the stream.
   ///
   /// \param corked The flag indicating whether the initial metadata is to be
@@ -331,8 +331,9 @@
     return census_context_;
   }
 
-  /// Send a best-effort out-of-band cancel. The call could be in any stage.
-  /// e.g. if it is already finished, it may still return success.
+  /// Send a best-effort out-of-band cancel on the call associated with
+  /// this client context.  The call could be in any stage; e.g., if it is
+  /// already finished, it may still return success.
   ///
   /// There is no guarantee the call will be cancelled.
   void TryCancel();
diff --git a/include/grpc++/impl/server_builder_plugin.h b/include/grpc++/impl/server_builder_plugin.h
index 8f2bce5..d940f8c 100644
--- a/include/grpc++/impl/server_builder_plugin.h
+++ b/include/grpc++/impl/server_builder_plugin.h
@@ -40,6 +40,7 @@
 
 namespace grpc {
 
+class ServerBuilder;
 class ServerInitializer;
 class ChannelArguments;
 
@@ -51,6 +52,10 @@
   virtual ~ServerBuilderPlugin() {}
   virtual grpc::string name() = 0;
 
+  /// UpdateServerBuilder will be called at the beginning of
+  /// \a ServerBuilder::BuildAndStart().
+  virtual void UpdateServerBuilder(ServerBuilder* builder) {}
+
   /// InitServer will be called in ServerBuilder::BuildAndStart(), after the
   /// Server instance is created.
   virtual void InitServer(ServerInitializer* si) = 0;
diff --git a/include/grpc++/resource_quota.h b/include/grpc++/resource_quota.h
index 1199ae9..fab2b4c 100644
--- a/include/grpc++/resource_quota.h
+++ b/include/grpc++/resource_quota.h
@@ -42,9 +42,10 @@
 namespace grpc {
 
 /// ResourceQuota represents a bound on memory usage by the gRPC library.
-/// A ResourceQuota can be attached to a server (via ServerBuilder), or a client
-/// channel (via ChannelArguments). gRPC will attempt to keep memory used by
-/// all attached entities below the ResourceQuota bound.
+/// A ResourceQuota can be attached to a server (via \a ServerBuilder),
+/// or a client channel (via \a ChannelArguments).
+/// gRPC will attempt to keep memory used by all attached entities
+/// below the ResourceQuota bound.
 class ResourceQuota final : private GrpcLibraryCodegen {
  public:
   /// \param name - a unique name for this ResourceQuota.
@@ -52,10 +53,10 @@
   ResourceQuota();
   ~ResourceQuota();
 
-  /// Resize this ResourceQuota to a new size. If new_size is smaller than the
-  /// current size of the pool, memory usage will be monotonically decreased
-  /// until it falls under new_size. No time bound is given for this to occur
-  /// however.
+  /// Resize this \a ResourceQuota to a new size. If \a new_size is smaller
+  /// than the current size of the pool, memory usage will be monotonically
+  /// decreased until it falls under \a new_size.
+  /// No time bound is given for this to occur however.
   ResourceQuota& Resize(size_t new_size);
 
   grpc_resource_quota* c_resource_quota() const { return impl_; }
diff --git a/include/grpc++/security/auth_metadata_processor.h b/include/grpc++/security/auth_metadata_processor.h
index 3536923..7df7965 100644
--- a/include/grpc++/security/auth_metadata_processor.h
+++ b/include/grpc++/security/auth_metadata_processor.h
@@ -42,6 +42,9 @@
 
 namespace grpc {
 
+/// Interface allowing custom server-side authorization based on credentials
+/// encoded in metadata.  Objects of this type can be passed to
+/// \a ServerCredentials::SetAuthMetadataProcessor().
 class AuthMetadataProcessor {
  public:
   typedef std::multimap<grpc::string_ref, grpc::string_ref> InputMetadata;
@@ -49,7 +52,7 @@
 
   virtual ~AuthMetadataProcessor() {}
 
-  /// If this method returns true, the Process function will be scheduled in
+  /// If this method returns true, the \a Process function will be scheduled in
   /// a different thread from the one processing the call.
   virtual bool IsBlocking() const { return true; }
 
diff --git a/include/grpc++/security/credentials.h b/include/grpc++/security/credentials.h
index 8d9d181..46cc96e 100644
--- a/include/grpc++/security/credentials.h
+++ b/include/grpc++/security/credentials.h
@@ -151,7 +151,7 @@
 /// json_key is the JSON key string containing the client's private key.
 /// token_lifetime_seconds is the lifetime in seconds of each Json Web Token
 /// (JWT) created with this credentials. It should not exceed
-/// grpc_max_auth_token_lifetime or will be cropped to this value.
+/// \a grpc_max_auth_token_lifetime or will be cropped to this value.
 std::shared_ptr<CallCredentials> ServiceAccountJWTAccessCredentials(
     const grpc::string& json_key, long token_lifetime_seconds);
 
diff --git a/include/grpc++/security/server_credentials.h b/include/grpc++/security/server_credentials.h
index 4676b04..d7720e4 100644
--- a/include/grpc++/security/server_credentials.h
+++ b/include/grpc++/security/server_credentials.h
@@ -70,7 +70,7 @@
 
 /// Options to create ServerCredentials with SSL
 struct SslServerCredentialsOptions {
-  /// Deprecated
+  /// \warning Deprecated
   SslServerCredentialsOptions()
       : force_client_auth(false),
         client_certificate_request(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE) {}
@@ -84,12 +84,13 @@
   };
   grpc::string pem_root_certs;
   std::vector<PemKeyCertPair> pem_key_cert_pairs;
-  /// Deprecated
+  /// \warning Deprecated
   bool force_client_auth;
 
-  /// If both force_client_auth and client_certificate_request fields are set,
-  /// force_client_auth takes effect i.e
-  /// REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY will be enforced.
+  /// If both \a force_client_auth and \a client_certificate_request
+  /// fields are set, \a force_client_auth takes effect, i.e.
+  /// \a REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
+  /// will be enforced.
   grpc_ssl_client_certificate_request_type client_certificate_request;
 };
 
diff --git a/include/grpc++/server.h b/include/grpc++/server.h
index 2f7018e..7f5a883 100644
--- a/include/grpc++/server.h
+++ b/include/grpc++/server.h
@@ -60,23 +60,26 @@
 class ServerContext;
 class ServerInitializer;
 
-/// Models a gRPC server.
+/// Represents a gRPC server.
 ///
-/// Servers are configured and started via \a grpc::ServerBuilder.
+/// Use a \a grpc::ServerBuilder to create, configure, and start
+/// \a Server instances.
 class Server final : public ServerInterface, private GrpcLibraryCodegen {
  public:
   ~Server();
 
-  /// Block waiting for all work to complete.
+  /// Block until the server shuts down.
   ///
   /// \warning The server must be either shutting down or some other thread must
   /// call \a Shutdown for this function to ever return.
   void Wait() override;
 
-  /// Global Callbacks
-  ///
-  /// Can be set exactly once per application to install hooks whenever
-  /// a server event occurs
+  /// Global callbacks are a set of hooks that are called when server
+  /// events occur.  \a SetGlobalCallbacks method is used to register
+  /// the hooks with gRPC.  Note that
+  /// the \a GlobalCallbacks instance will be shared among all
+  /// \a Server instances in an application and can be set exactly
+  /// once per application.
   class GlobalCallbacks {
    public:
     virtual ~GlobalCallbacks() {}
@@ -92,12 +95,14 @@
     virtual void AddPort(Server* server, const grpc::string& addr,
                          ServerCredentials* creds, int port) {}
   };
-  /// Set the global callback object. Can only be called once. Does not take
-  /// ownership of callbacks, and expects the pointed to object to be alive
-  /// until all server objects in the process have been destroyed.
+  /// Set the global callback object. Can only be called once per application.
+  /// Does not take ownership of callbacks, and expects the pointed to object
+  /// to be alive until all server objects in the process have been destroyed.
+  /// The same \a GlobalCallbacks object will be used throughout the
+  /// application and is shared among all \a Server objects.
   static void SetGlobalCallbacks(GlobalCallbacks* callbacks);
 
-  // Returns a \em raw pointer to the underlying grpc_server instance.
+  // Returns a \em raw pointer to the underlying \a grpc_server instance.
   grpc_server* c_server();
 
   /// Returns the health check service.
@@ -158,17 +163,19 @@
   /// service. The service must exist for the lifetime of the Server instance.
   void RegisterAsyncGenericService(AsyncGenericService* service) override;
 
-  /// Tries to bind \a server to the given \a addr.
+  /// Try binding the server to the given \a addr endpoint
+  /// (port, and optionally including IP address to bind to).
   ///
-  /// It can be invoked multiple times.
+  /// It can be invoked multiple times. Should be used before
+  /// starting the server.
   ///
   /// \param addr The address to try to bind to the server (eg, localhost:1234,
   /// 192.168.1.1:31416, [::1]:27182, etc.).
   /// \params creds The credentials associated with the server.
   ///
-  /// \return bound port number on sucess, 0 on failure.
+  /// \return bound port number on success, 0 on failure.
   ///
-  /// \warning It's an error to call this method on an already started server.
+  /// \warning It is an error to call this method on an already started server.
   int AddListeningPort(const grpc::string& addr,
                        ServerCredentials* creds) override;
 
@@ -194,13 +201,14 @@
 
   const int max_receive_message_size_;
 
-  /// The following completion queues are ONLY used in case of Sync API i.e if
-  /// the server has any services with sync methods. The server uses these
-  /// completion queues to poll for new RPCs
+  /// The following completion queues are ONLY used in case of Sync API
+  /// i.e. if the server has any services with sync methods. The server uses
+  /// these completion queues to poll for new RPCs
   std::shared_ptr<std::vector<std::unique_ptr<ServerCompletionQueue>>>
       sync_server_cqs_;
 
-  /// List of ThreadManager instances (one for each cq in the sync_server_cqs)
+  /// List of \a ThreadManager instances (one for each cq in
+  /// the \a sync_server_cqs)
   std::vector<std::unique_ptr<SyncRequestThreadManager>> sync_req_mgrs_;
 
   // Sever status
diff --git a/include/grpc++/server_builder.h b/include/grpc++/server_builder.h
index 2185b28..f8b6d03 100644
--- a/include/grpc++/server_builder.h
+++ b/include/grpc++/server_builder.h
@@ -71,7 +71,13 @@
   ServerBuilder();
   ~ServerBuilder();
 
-  enum SyncServerOption { NUM_CQS, MIN_POLLERS, MAX_POLLERS, CQ_TIMEOUT_MSEC };
+  /// Options for synchronous servers.
+  enum SyncServerOption {
+    NUM_CQS,         ///< Number of completion queues.
+    MIN_POLLERS,     ///< Minimum number of polling threads.
+    MAX_POLLERS,     ///< Maximum number of polling threads.
+    CQ_TIMEOUT_MSEC  ///< Completion queue timeout in milliseconds.
+  };
 
   /// Register a service. This call does not take ownership of the service.
   /// The service must exist for the lifetime of the \a Server instance returned
@@ -85,7 +91,7 @@
 
   /// Register a service. This call does not take ownership of the service.
   /// The service must exist for the lifetime of the \a Server instance returned
-  /// by BuildAndStart().
+  /// by \a BuildAndStart().
   /// Only matches requests with :authority \a host
   ServerBuilder& RegisterService(const grpc::string& host, Service* service);
 
@@ -110,7 +116,7 @@
   /// enabled by default.
   ///
   /// Incoming calls compressed with an unsupported algorithm will fail with
-  /// GRPC_STATUS_UNIMPLEMENTED.
+  /// \a GRPC_STATUS_UNIMPLEMENTED.
   ServerBuilder& SetCompressionAlgorithmSupportStatus(
       grpc_compression_algorithm algorithm, bool enabled);
 
@@ -139,15 +145,17 @@
     return SetOption(MakeChannelArgumentOption(arg, value));
   }
 
-  /// Tries to bind \a server to the given \a addr.
+  /// Enlists an endpoint \a addr (port with an optional IP address) to
+  /// bind the \a grpc::Server object to be created to.
   ///
   /// It can be invoked multiple times.
   ///
   /// \param addr The address to try to bind to the server (eg, localhost:1234,
   /// 192.168.1.1:31416, [::1]:27182, etc.).
   /// \params creds The credentials associated with the server.
-  /// \param selected_port[out] Upon success, updated to contain the port
-  /// number. \a nullptr otherwise.
+  /// \param selected_port[out] If not `nullptr`, gets populated with the port
+  /// number bound to the \a grpc::Server for the corresponding endpoint after
+  /// it is successfully bound, 0 otherwise.
   ///
   // TODO(dgq): the "port" part seems to be a misnomer.
   ServerBuilder& AddListeningPort(const grpc::string& addr,
@@ -169,12 +177,13 @@
   /// server_->Shutdown();
   /// cq_->Shutdown();  // Always *after* the associated server's Shutdown()!
   ///
-  /// \param is_frequently_polled This is an optional parameter to inform GRPC
+  /// \param is_frequently_polled This is an optional parameter to inform gRPC
   /// library about whether this completion queue would be frequently polled
-  /// (i.e by calling Next() or AsyncNext()). The default value is 'true' and is
-  /// the recommended setting. Setting this to 'false' (i.e not polling the
-  /// completion queue frequently) will have a significantly negative
-  /// performance impact and hence should not be used in production use cases.
+  /// (i.e. by calling \a Next() or \a AsyncNext()). The default value is
+  /// 'true' and is the recommended setting. Setting this to 'false' (i.e.
+  /// not polling the completion queue frequently) will have a significantly
+  /// negative performance impact and hence should not be used in production
+  /// use cases.
   std::unique_ptr<ServerCompletionQueue> AddCompletionQueue(
       bool is_frequently_polled = true);
 
@@ -206,18 +215,18 @@
           max_pollers(2),
           cq_timeout_msec(10000) {}
 
-    // Number of server completion queues to create to listen to incoming RPCs.
+    /// Number of server completion queues to create to listen to incoming RPCs.
     int num_cqs;
 
-    // Minimum number of threads per completion queue that should be listening
-    // to incoming RPCs.
+    /// Minimum number of threads per completion queue that should be listening
+    /// to incoming RPCs.
     int min_pollers;
 
-    // Maximum number of threads per completion queue that can be listening to
-    // incoming RPCs.
+    /// Maximum number of threads per completion queue that can be listening to
+    /// incoming RPCs.
     int max_pollers;
 
-    // The timeout for server completion queue's AsyncNext call.
+    /// The timeout for server completion queue's AsyncNext call.
     int cq_timeout_msec;
   };
 
@@ -238,7 +247,7 @@
 
   SyncServerSettings sync_server_settings_;
 
-  // List of completion queues added via AddCompletionQueue() method
+  /// List of completion queues added via \a AddCompletionQueue method.
   std::vector<ServerCompletionQueue*> cqs_;
 
   std::shared_ptr<ServerCredentials> creds_;
diff --git a/include/grpc++/server_posix.h b/include/grpc++/server_posix.h
index e6066d4..a8fe8cb 100644
--- a/include/grpc++/server_posix.h
+++ b/include/grpc++/server_posix.h
@@ -43,9 +43,10 @@
 
 #ifdef GPR_SUPPORT_CHANNELS_FROM_FD
 
-/// Adds new client to a \a Server communicating over given file descriptor
+/// Add a new client to a \a Server communicating over the given
+/// file descriptor.
 ///
-/// \param server The server to add a client to.
+/// \param server The server to add the client to.
 /// \param fd The file descriptor representing a socket.
 void AddInsecureChannelFromFd(Server* server, int fd);
 
diff --git a/include/grpc++/support/channel_arguments.h b/include/grpc++/support/channel_arguments.h
index 61307d6..061ab55 100644
--- a/include/grpc++/support/channel_arguments.h
+++ b/include/grpc++/support/channel_arguments.h
@@ -49,7 +49,7 @@
 class ResourceQuota;
 
 /// Options for channel creation. The user can use generic setters to pass
-/// key value pairs down to c channel creation code. For grpc related options,
+/// key value pairs down to C channel creation code. For gRPC related options,
 /// concrete setters are provided.
 class ChannelArguments {
  public:
@@ -82,13 +82,13 @@
   /// Set the socket mutator for the channel.
   void SetSocketMutator(grpc_socket_mutator* mutator);
 
-  /// The given string will be sent at the front of the user agent string.
+  /// Set the string to prepend to the user agent.
   void SetUserAgentPrefix(const grpc::string& user_agent_prefix);
 
-  /// The given buffer pool will be attached to the constructed channel
+  /// Set the buffer pool to be attached to the constructed channel.
   void SetResourceQuota(const ResourceQuota& resource_quota);
 
-  /// Sets the max receive and send message sizes.
+  /// Set the max receive and send message sizes.
   void SetMaxReceiveMessageSize(int size);
   void SetMaxSendMessageSize(int size);
 
@@ -115,8 +115,8 @@
   /// Set a textual argument \a value under \a key.
   void SetString(const grpc::string& key, const grpc::string& value);
 
-  /// Return (by value) a c grpc_channel_args structure which points to
-  /// arguments owned by this ChannelArguments instance
+  /// Return (by value) a C \a grpc_channel_args structure which points to
+  /// arguments owned by this \a ChannelArguments instance
   grpc_channel_args c_channel_args() const {
     grpc_channel_args out;
     out.num_args = args_.size();
diff --git a/include/grpc++/support/error_details.h b/include/grpc++/support/error_details.h
index d4324d3..4f51762 100644
--- a/include/grpc++/support/error_details.h
+++ b/include/grpc++/support/error_details.h
@@ -44,16 +44,16 @@
 
 namespace grpc {
 
-/// Maps a grpc::Status to a google::rpc::Status.
+/// Map a \a grpc::Status to a \a google::rpc::Status.
 /// The given \a to object will be cleared.
 /// On success, returns status with OK.
-/// Returns status with INVALID_ARGUMENT, if failed to deserialize.
-/// Returns status with FAILED_PRECONDITION, if \a to is nullptr.
+/// Returns status with \a INVALID_ARGUMENT, if failed to deserialize.
+/// Returns status with \a FAILED_PRECONDITION, if \a to is nullptr.
 Status ExtractErrorDetails(const Status& from, ::google::rpc::Status* to);
 
-/// Maps google::rpc::Status to a grpc::Status.
+/// Map \a google::rpc::Status to a \a grpc::Status.
 /// Returns OK on success.
-/// Returns status with FAILED_PRECONDITION if \a to is nullptr.
+/// Returns status with \a FAILED_PRECONDITION if \a to is nullptr.
 Status SetErrorDetails(const ::google::rpc::Status& from, Status* to);
 
 }  // namespace grpc
diff --git a/include/grpc++/support/slice.h b/include/grpc++/support/slice.h
index 3ec0d1a..e2bd211 100644
--- a/include/grpc++/support/slice.h
+++ b/include/grpc++/support/slice.h
@@ -48,7 +48,7 @@
  public:
   /// Construct an empty slice.
   Slice();
-  // Destructor - drops one reference.
+  /// Destructor - drops one reference.
   ~Slice();
 
   enum AddRef { ADD_REF };
diff --git a/include/grpc++/test/server_context_test_spouse.h b/include/grpc++/test/server_context_test_spouse.h
index 5bd07e7..80edd56 100644
--- a/include/grpc++/test/server_context_test_spouse.h
+++ b/include/grpc++/test/server_context_test_spouse.h
@@ -47,7 +47,7 @@
   explicit ServerContextTestSpouse(ServerContext* ctx) : ctx_(ctx) {}
 
   /// Inject client metadata to the ServerContext for the test. The test spouse
-  /// must be alive when ServerContext::client_metadata is called.
+  /// must be alive when \a ServerContext::client_metadata is called.
   void AddClientMetadata(const grpc::string& key, const grpc::string& value) {
     client_metadata_storage_.insert(
         std::pair<grpc::string, grpc::string>(key, value));
@@ -70,7 +70,7 @@
   }
 
  private:
-  ServerContext* ctx_;  /// not owned
+  ServerContext* ctx_;  // not owned
   std::multimap<grpc::string, grpc::string> client_metadata_storage_;
 };
 
diff --git a/include/grpc/grpc.h b/include/grpc/grpc.h
index a36367f..47d1ea6 100644
--- a/include/grpc/grpc.h
+++ b/include/grpc/grpc.h
@@ -178,6 +178,12 @@
 GRPCAPI grpc_connectivity_state grpc_channel_check_connectivity_state(
     grpc_channel *channel, int try_to_connect);
 
+/** Number of active "external connectivity state watchers" attached to a
+ * channel.
+ * Useful for testing. **/
+GRPCAPI int grpc_channel_num_external_connectivity_watchers(
+    grpc_channel *channel);
+
 /** Watch for a change in connectivity state.
     Once the channel connectivity state is different from last_observed_state,
     tag will be enqueued on cq with success=1.
diff --git a/include/grpc/support/log.h b/include/grpc/support/log.h
index 917b011..62324d8 100644
--- a/include/grpc/support/log.h
+++ b/include/grpc/support/log.h
@@ -65,7 +65,7 @@
 #define GPR_LOG_VERBOSITY_UNSET -1
 
 /** Returns a string representation of the log severity */
-const char *gpr_log_severity_string(gpr_log_severity severity);
+GPRAPI const char *gpr_log_severity_string(gpr_log_severity severity);
 
 /** Macros to build log contexts at various severity levels */
 #define GPR_DEBUG __FILE__, __LINE__, GPR_LOG_SEVERITY_DEBUG
diff --git a/package.json b/package.json
index 1ed182c..c78040e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "grpc",
-  "version": "1.4.0-dev",
+  "version": "1.4.0-pre1",
   "author": "Google Inc.",
   "description": "gRPC Library for Node",
   "homepage": "http://www.grpc.io/",
@@ -33,7 +33,7 @@
     "arguejs": "^0.2.3",
     "lodash": "^4.15.0",
     "nan": "^2.0.0",
-    "node-pre-gyp": "^0.6.0",
+    "node-pre-gyp": "^0.6.35",
     "protobufjs": "^5.0.0"
   },
   "devDependencies": {
diff --git a/package.xml b/package.xml
index e560139..5d6c089 100644
--- a/package.xml
+++ b/package.xml
@@ -10,11 +10,11 @@
   <email>grpc-packages@google.com</email>
   <active>yes</active>
  </lead>
- <date>2017-05-05</date>
+ <date>2017-05-22</date>
  <time>16:06:07</time>
  <version>
-  <release>1.4.0dev</release>
-  <api>1.4.0dev</api>
+  <release>1.4.0RC1</release>
+  <api>1.4.0RC1</api>
  </version>
  <stability>
   <release>beta</release>
@@ -23,13 +23,18 @@
  <license>BSD</license>
  <notes>
 - Fixed some memory leaks #9559, #10996
+- Disabled cares dependency from gRPC C Core #10940
+- De-coupled protobuf dependency #11112
+- Fixed extension reported version #10842
+- Added config.w32 for Windows support #8161
+- Fixed PHP distrib test after cc files were added #11193
+- Fixed protoc plugin comment escape bug #11025
  </notes>
  <contents>
   <dir baseinstalldir="/" name="/">
     <file baseinstalldir="/" name="config.m4" role="src" />
+    <file baseinstalldir="/" name="config.w32" role="src" />
     <file baseinstalldir="/" name="src/php/README.md" role="src" />
-    <file baseinstalldir="/" name="src/php/ext/grpc/CREDITS" role="src" />
-    <file baseinstalldir="/" name="src/php/ext/grpc/LICENSE" role="src" />
     <file baseinstalldir="/" name="src/php/ext/grpc/byte_buffer.c" role="src" />
     <file baseinstalldir="/" name="src/php/ext/grpc/call.c" role="src" />
     <file baseinstalldir="/" name="src/php/ext/grpc/call_credentials.c" role="src" />
@@ -388,6 +393,8 @@
     <file baseinstalldir="/" name="src/core/ext/census/gen/census.pb.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/gen/trace_context.pb.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/grpc_filter.h" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/census/intrusive_hash_map.h" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/census/intrusive_hash_map_internal.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/mlog.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/resource.h" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/rpc_metric_id.h" role="src" />
@@ -638,6 +645,7 @@
     <file baseinstalldir="/" name="src/core/ext/census/grpc_filter.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/grpc_plugin.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/initialize.c" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/census/intrusive_hash_map.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/mlog.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/operation.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/census/placeholders.c" role="src" />
@@ -1057,6 +1065,32 @@
     <file baseinstalldir="/" name="third_party/boringssl/ssl/tls13_server.c" role="src" />
     <file baseinstalldir="/" name="third_party/boringssl/ssl/tls_method.c" role="src" />
     <file baseinstalldir="/" name="third_party/boringssl/ssl/tls_record.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/crc32.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/deflate.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/gzguts.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/inffast.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/inffixed.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/inflate.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/inftrees.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/trees.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/zconf.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/zlib.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/zutil.h" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/adler32.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/compress.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/crc32.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/deflate.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/gzclose.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/gzlib.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/gzread.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/gzwrite.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/infback.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/inffast.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/inflate.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/inftrees.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/trees.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/uncompr.c" role="src" />
+    <file baseinstalldir="/" name="third_party/zlib/zutil.c" role="src" />
     <file name="LICENSE" role="doc" />
   </dir>
  </contents>
diff --git a/src/compiler/php_generator.cc b/src/compiler/php_generator.cc
index 7d51d40..67c4c80 100644
--- a/src/compiler/php_generator.cc
+++ b/src/compiler/php_generator.cc
@@ -67,12 +67,11 @@
   vars["input_type_id"] = MessageIdentifierName(input_type->full_name());
   vars["output_type_id"] = MessageIdentifierName(output_type->full_name());
 
-  out->Print("/**\n");
-  out->Print(GetPHPComments(method, " *").c_str());
+  out->Print(GetPHPComments(method, " //").c_str());
   if (method->client_streaming()) {
     out->Print(vars,
-               " * @param array $$metadata metadata\n"
-               " * @param array $$options call options\n */\n"
+               " // @param array $$metadata metadata\n"
+               " // @param array $$options call options\n"
                "public function $name$($$metadata = [], "
                "$$options = []) {\n");
     out->Indent();
@@ -87,9 +86,9 @@
                "$$metadata, $$options);\n");
   } else {
     out->Print(vars,
-               " * @param \\$input_type_id$ $$argument input argument\n"
-               " * @param array $$metadata metadata\n"
-               " * @param array $$options call options\n */\n"
+               " // @param \\$input_type_id$ $$argument input argument\n"
+               " // @param array $$metadata metadata\n"
+               " // @param array $$options call options\n"
                "public function $name$(\\$input_type_id$ $$argument,\n"
                "  $$metadata = [], $$options = []) {\n");
     out->Indent();
@@ -116,10 +115,10 @@
   out->Print(vars, "class $name$Client extends \\Grpc\\BaseStub {\n\n");
   out->Indent();
   out->Print(
-      "/**\n * @param string $$hostname hostname\n"
-      " * @param array $$opts channel options\n"
-      " * @param \\Grpc\\Channel $$channel (optional) re-use channel "
-      "object\n */\n"
+      " // @param string $$hostname hostname\n"
+      " // @param array $$opts channel options\n"
+      " // @param \\Grpc\\Channel $$channel (optional) re-use channel "
+      "object\n"
       "public function __construct($$hostname, $$opts, "
       "$$channel = null) {\n");
   out->Indent();
diff --git a/src/core/ext/census/intrusive_hash_map.c b/src/core/ext/census/intrusive_hash_map.c
new file mode 100644
index 0000000..77512a3
--- /dev/null
+++ b/src/core/ext/census/intrusive_hash_map.c
@@ -0,0 +1,319 @@
+/*
+ *
+ * Copyright 2017, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/ext/census/intrusive_hash_map.h"
+#include <string.h>
+
+extern bool hm_index_compare(const hm_index *A, const hm_index *B);
+
+/* Simple hashing function that takes lower 32 bits. */
+static inline uint32_t chunked_vector_hasher(uint64_t key) {
+  return (uint32_t)key;
+}
+
+/* Vector chunks are 1MiB divided by pointer size. */
+static const size_t VECTOR_CHUNK_SIZE = (1 << 20) / sizeof(void *);
+
+/* Helper functions which return buckets from the chunked vector. */
+static inline void **get_mutable_bucket(const chunked_vector *buckets,
+                                        uint32_t index) {
+  if (index < VECTOR_CHUNK_SIZE) {
+    return &buckets->first_[index];
+  }
+  size_t rest_index = (index - VECTOR_CHUNK_SIZE) / VECTOR_CHUNK_SIZE;
+  return &buckets->rest_[rest_index][index % VECTOR_CHUNK_SIZE];
+}
+
+static inline void *get_bucket(const chunked_vector *buckets, uint32_t index) {
+  if (index < VECTOR_CHUNK_SIZE) {
+    return buckets->first_[index];
+  }
+  size_t rest_index = (index - VECTOR_CHUNK_SIZE) / VECTOR_CHUNK_SIZE;
+  return buckets->rest_[rest_index][index % VECTOR_CHUNK_SIZE];
+}
+
+/* Helper function. */
+static inline size_t RestSize(const chunked_vector *vec) {
+  return (vec->size_ <= VECTOR_CHUNK_SIZE)
+             ? 0
+             : (vec->size_ - VECTOR_CHUNK_SIZE - 1) / VECTOR_CHUNK_SIZE + 1;
+}
+
+/* Initialize chunked vector to size of 0. */
+static void chunked_vector_init(chunked_vector *vec) {
+  vec->size_ = 0;
+  vec->first_ = NULL;
+  vec->rest_ = NULL;
+}
+
+/* Clear chunked vector and free all memory that has been allocated then
+   initialize chunked vector. */
+static void chunked_vector_clear(chunked_vector *vec) {
+  if (vec->first_ != NULL) {
+    gpr_free(vec->first_);
+  }
+  if (vec->rest_ != NULL) {
+    size_t rest_size = RestSize(vec);
+    for (size_t i = 0; i < rest_size; ++i) {
+      if (vec->rest_[i] != NULL) {
+        gpr_free(vec->rest_[i]);
+      }
+    }
+    gpr_free(vec->rest_);
+  }
+  chunked_vector_init(vec);
+}
+
+/* Clear chunked vector and then resize it to n entries. Allow the first 1MB to
+   be read w/o an extra cache miss. The rest of the elements are stored in an
+   array of arrays to avoid large mallocs. */
+static void chunked_vector_reset(chunked_vector *vec, size_t n) {
+  chunked_vector_clear(vec);
+  vec->size_ = n;
+  if (n <= VECTOR_CHUNK_SIZE) {
+    vec->first_ = (void **)gpr_malloc(sizeof(void *) * n);
+    memset(vec->first_, 0, sizeof(void *) * n);
+  } else {
+    vec->first_ = (void **)gpr_malloc(sizeof(void *) * VECTOR_CHUNK_SIZE);
+    memset(vec->first_, 0, sizeof(void *) * VECTOR_CHUNK_SIZE);
+    size_t rest_size = RestSize(vec);
+    vec->rest_ = (void ***)gpr_malloc(sizeof(void **) * rest_size);
+    memset(vec->rest_, 0, sizeof(void **) * rest_size);
+    int i = 0;
+    n -= VECTOR_CHUNK_SIZE;
+    while (n > 0) {
+      size_t this_size = GPR_MIN(n, VECTOR_CHUNK_SIZE);
+      vec->rest_[i] = (void **)gpr_malloc(sizeof(void *) * this_size);
+      memset(vec->rest_[i], 0, sizeof(void *) * this_size);
+      n -= this_size;
+      ++i;
+    }
+  }
+}
+
+void intrusive_hash_map_init(intrusive_hash_map *hash_map,
+                             uint32_t initial_log2_table_size) {
+  hash_map->log2_num_buckets = initial_log2_table_size;
+  hash_map->num_items = 0;
+  uint32_t num_buckets = (uint32_t)1 << hash_map->log2_num_buckets;
+  hash_map->extend_threshold = num_buckets >> 1;
+  chunked_vector_init(&hash_map->buckets);
+  chunked_vector_reset(&hash_map->buckets, num_buckets);
+  hash_map->hash_mask = num_buckets - 1;
+}
+
+bool intrusive_hash_map_empty(const intrusive_hash_map *hash_map) {
+  return hash_map->num_items == 0;
+}
+
+size_t intrusive_hash_map_size(const intrusive_hash_map *hash_map) {
+  return hash_map->num_items;
+}
+
+void intrusive_hash_map_end(const intrusive_hash_map *hash_map, hm_index *idx) {
+  idx->bucket_index = (uint32_t)hash_map->buckets.size_;
+  GPR_ASSERT(idx->bucket_index <= UINT32_MAX);
+  idx->item = NULL;
+}
+
+void intrusive_hash_map_next(const intrusive_hash_map *hash_map,
+                             hm_index *idx) {
+  idx->item = idx->item->hash_link;
+  while (idx->item == NULL) {
+    idx->bucket_index++;
+    if (idx->bucket_index >= hash_map->buckets.size_) {
+      /* Reached end of table. */
+      idx->item = NULL;
+      return;
+    }
+    idx->item = (hm_item *)get_bucket(&hash_map->buckets, idx->bucket_index);
+  }
+}
+
+void intrusive_hash_map_begin(const intrusive_hash_map *hash_map,
+                              hm_index *idx) {
+  for (uint32_t i = 0; i < hash_map->buckets.size_; ++i) {
+    if (get_bucket(&hash_map->buckets, i) != NULL) {
+      idx->bucket_index = i;
+      idx->item = (hm_item *)get_bucket(&hash_map->buckets, i);
+      return;
+    }
+  }
+  intrusive_hash_map_end(hash_map, idx);
+}
+
+hm_item *intrusive_hash_map_find(const intrusive_hash_map *hash_map,
+                                 uint64_t key) {
+  uint32_t index = chunked_vector_hasher(key) & hash_map->hash_mask;
+
+  hm_item *p = (hm_item *)get_bucket(&hash_map->buckets, index);
+  while (p != NULL) {
+    if (key == p->key) {
+      return p;
+    }
+    p = p->hash_link;
+  }
+  return NULL;
+}
+
+hm_item *intrusive_hash_map_erase(intrusive_hash_map *hash_map, uint64_t key) {
+  uint32_t index = chunked_vector_hasher(key) & hash_map->hash_mask;
+
+  hm_item **slot = (hm_item **)get_mutable_bucket(&hash_map->buckets, index);
+  hm_item *p = *slot;
+  if (p == NULL) {
+    return NULL;
+  }
+
+  if (key == p->key) {
+    *slot = p->hash_link;
+    p->hash_link = NULL;
+    hash_map->num_items--;
+    return p;
+  }
+
+  hm_item *prev = p;
+  p = p->hash_link;
+
+  while (p) {
+    if (key == p->key) {
+      prev->hash_link = p->hash_link;
+      p->hash_link = NULL;
+      hash_map->num_items--;
+      return p;
+    }
+    prev = p;
+    p = p->hash_link;
+  }
+  return NULL;
+}
+
+/* Insert an hm_item* into the underlying chunked vector. hash_mask is
+ * array_size-1. Returns true if it is a new hm_item and false if the hm_item
+ * already existed.
+ */
+static inline bool intrusive_hash_map_internal_insert(chunked_vector *buckets,
+                                                      uint32_t hash_mask,
+                                                      hm_item *item) {
+  const uint64_t key = item->key;
+  uint32_t index = chunked_vector_hasher(key) & hash_mask;
+  hm_item **slot = (hm_item **)get_mutable_bucket(buckets, index);
+  hm_item *p = *slot;
+  item->hash_link = p;
+
+  /* Check to see if key already exists. */
+  while (p) {
+    if (p->key == key) {
+      return false;
+    }
+    p = p->hash_link;
+  }
+
+  /* Otherwise add new entry. */
+  *slot = item;
+  return true;
+}
+
+/* Extend the allocated number of elements in the hash map by a factor of 2. */
+void intrusive_hash_map_extend(intrusive_hash_map *hash_map) {
+  uint32_t new_log2_num_buckets = 1 + hash_map->log2_num_buckets;
+  uint32_t new_num_buckets = (uint32_t)1 << new_log2_num_buckets;
+  GPR_ASSERT(new_num_buckets <= UINT32_MAX && new_num_buckets > 0);
+  chunked_vector new_buckets;
+  chunked_vector_init(&new_buckets);
+  chunked_vector_reset(&new_buckets, new_num_buckets);
+  uint32_t new_hash_mask = new_num_buckets - 1;
+
+  hm_index cur_idx;
+  hm_index end_idx;
+  intrusive_hash_map_end(hash_map, &end_idx);
+  intrusive_hash_map_begin(hash_map, &cur_idx);
+  while (!hm_index_compare(&cur_idx, &end_idx)) {
+    hm_item *new_item = cur_idx.item;
+    intrusive_hash_map_next(hash_map, &cur_idx);
+    intrusive_hash_map_internal_insert(&new_buckets, new_hash_mask, new_item);
+  }
+
+  /* Set values for new chunked_vector. extend_threshold is set to half of
+   * new_num_buckets. */
+  hash_map->log2_num_buckets = new_log2_num_buckets;
+  chunked_vector_clear(&hash_map->buckets);
+  hash_map->buckets = new_buckets;
+  hash_map->hash_mask = new_hash_mask;
+  hash_map->extend_threshold = new_num_buckets >> 1;
+}
+
+/* Insert a hm_item. The hm_item must remain live until it is removed from the
+   table. This object does not take the ownership of hm_item. The caller must
+   remove this hm_item from the table and delete it before this table is
+   deleted. If hm_item exists already num_items is not changed. */
+bool intrusive_hash_map_insert(intrusive_hash_map *hash_map, hm_item *item) {
+  if (hash_map->num_items >= hash_map->extend_threshold) {
+    intrusive_hash_map_extend(hash_map);
+  }
+  if (intrusive_hash_map_internal_insert(&hash_map->buckets,
+                                         hash_map->hash_mask, item)) {
+    hash_map->num_items++;
+    return true;
+  }
+  return false;
+}
+
+void intrusive_hash_map_clear(intrusive_hash_map *hash_map,
+                              void (*free_object)(void *)) {
+  hm_index cur;
+  hm_index end;
+  intrusive_hash_map_end(hash_map, &end);
+  intrusive_hash_map_begin(hash_map, &cur);
+
+  while (!hm_index_compare(&cur, &end)) {
+    hm_index next = cur;
+    intrusive_hash_map_next(hash_map, &next);
+    if (cur.item != NULL) {
+      hm_item *item = intrusive_hash_map_erase(hash_map, cur.item->key);
+      (*free_object)((void *)item);
+      gpr_free(item);
+    }
+    cur = next;
+  }
+}
+
+void intrusive_hash_map_free(intrusive_hash_map *hash_map,
+                             void (*free_object)(void *)) {
+  intrusive_hash_map_clear(hash_map, (*free_object));
+  hash_map->num_items = 0;
+  hash_map->extend_threshold = 0;
+  hash_map->log2_num_buckets = 0;
+  hash_map->hash_mask = 0;
+  chunked_vector_clear(&hash_map->buckets);
+}
diff --git a/src/core/ext/census/intrusive_hash_map.h b/src/core/ext/census/intrusive_hash_map.h
new file mode 100644
index 0000000..a840551
--- /dev/null
+++ b/src/core/ext/census/intrusive_hash_map.h
@@ -0,0 +1,167 @@
+/*
+ *
+ * Copyright 2017, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_H
+#define GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_H
+
+#include "src/core/ext/census/intrusive_hash_map_internal.h"
+
+/* intrusive_hash_map is a fast chained hash table. This hash map is faster than
+ * a dense hash map when the application calls insert and erase more often than
+ * find. When the workload is dominated by find() a dense hash map may be
+ * faster.
+ *
+ * intrusive_hash_map uses an intrusive header placed within a user defined
+ * struct. The header field IHM_key MUST be set to a valid value before
+ * insertion into the hash map or undefined behavior may occur. The header field
+ * IHM_hash_link MUST to be set to NULL initially.
+ *
+ * EXAMPLE USAGE:
+ *
+ *  typedef struct string_item {
+ *    INTRUSIVE_HASH_MAP_HEADER;
+ *    // User data.
+ *    char *str_buf;
+ *    uint16_t len;
+ *  } string_item;
+ *
+ *  static string_item *make_string_item(uint64_t key, const char *buf,
+ *                                       uint16_t len) {
+ *    string_item *item = (string_item *)gpr_malloc(sizeof(string_item));
+ *    item->IHM_key = key;
+ *    item->IHM_hash_link = NULL;
+ *    item->len = len;
+ *    item->str_buf = (char *)malloc(len);
+ *    memcpy(item->str_buf, buf, len);
+ *    return item;
+ *  }
+ *
+ *  intrusive_hash_map hash_map;
+ *  intrusive_hash_map_init(&hash_map, 4);
+ *  string_item *new_item1 = make_string_item(10, "test1", 5);
+ *  bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item1);
+ *
+ *  string_item *item1 =
+ *    (string_item *)intrusive_hash_map_find(&hash_map, 10);
+ */
+
+/* Hash map item. Stores key and a pointer to the actual object. A user defined
+ * version of this can be passed in provided the first 2 entries (key and
+ * hash_link) are the same. These entries must be first in the user defined
+ * struct. Pointer to struct will need to be cast as (hm_item *) when passed to
+ * hash map. This allows it to be intrusive. */
+typedef struct hm_item {
+  uint64_t key;
+  struct hm_item *hash_link;
+  /* Optional user defined data after this. */
+} hm_item;
+
+/* Macro provided for ease of use.  This must be first in the user defined
+ * struct (i.e. uint64_t key and hm_item * must be the first two elements in
+ * that order). */
+#define INTRUSIVE_HASH_MAP_HEADER \
+  uint64_t IHM_key;               \
+  struct hm_item *IHM_hash_link
+
+/* Index struct which acts as a pseudo-iterator within the hash map. */
+typedef struct hm_index {
+  uint32_t bucket_index;  // hash map bucket index.
+  hm_item *item;          // Pointer to hm_item within the hash map.
+} hm_index;
+
+/* Returns true if two hm_indices point to the same object within the hash map
+ * and false otherwise. */
+inline bool hm_index_compare(const hm_index *A, const hm_index *B) {
+  return (A->item == B->item && A->bucket_index == B->bucket_index);
+}
+
+/*
+ * Helper functions for iterating over the hash map.
+ */
+
+/* On return idx will contain an invalid index which is always equal to
+ * hash_map->buckets.size_ */
+void intrusive_hash_map_end(const intrusive_hash_map *hash_map, hm_index *idx);
+
+/* Iterates index to the next valid entry in the hash map and stores the
+ * index within idx. If end of table is reached, idx will contain the same
+ * values as if intrusive_hash_map_end() was called. */
+void intrusive_hash_map_next(const intrusive_hash_map *hash_map, hm_index *idx);
+
+/* On return, idx will contain the index of the first non-null entry in the hash
+ * map. If the hash map is empty, idx will contain the same values as if
+ * intrusive_hash_map_end() was called. */
+void intrusive_hash_map_begin(const intrusive_hash_map *hash_map,
+                              hm_index *idx);
+
+/* Initialize intrusive hash map data structure. This must be called before
+ * the hash map can be used. The initial size of an intrusive hash map will be
+ * 2^initial_log2_map_size (valid range is [0, 31]). */
+void intrusive_hash_map_init(intrusive_hash_map *hash_map,
+                             uint32_t initial_log2_map_size);
+
+/* Returns true if the hash map is empty and false otherwise. */
+bool intrusive_hash_map_empty(const intrusive_hash_map *hash_map);
+
+/* Returns the number of elements currently in the hash map. */
+size_t intrusive_hash_map_size(const intrusive_hash_map *hash_map);
+
+/* Find a hm_item within the hash map by key. Returns NULL if item was not
+ * found. */
+hm_item *intrusive_hash_map_find(const intrusive_hash_map *hash_map,
+                                 uint64_t key);
+
+/* Erase the hm_item that corresponds with key. If the hm_item is found, return
+ * the pointer to the hm_item. Else returns NULL. */
+hm_item *intrusive_hash_map_erase(intrusive_hash_map *hash_map, uint64_t key);
+
+/* Attempts to insert a new hm_item into the hash map.  If an element with the
+ * same key already exists, it will not insert the new item and return false.
+ * Otherwise, it will insert the new item and return true. */
+bool intrusive_hash_map_insert(intrusive_hash_map *hash_map, hm_item *item);
+
+/* Clears entire contents of the hash map, but leaves internal data structure
+ * untouched. Second argument takes a function pointer to a function that will
+ * free the object designated by the user and pointed to by hash_map->value. */
+void intrusive_hash_map_clear(intrusive_hash_map *hash_map,
+                              void (*free_object)(void *));
+
+/* Erase all contents of hash map and free the memory. Hash map is invalid
+ * after calling this function and cannot be used until it has been
+ * reinitialized (intrusive_hash_map_init()). This function takes a function
+ * pointer to a function that will free the object designated by the user and
+ * pointed to by hash_map->value. */
+void intrusive_hash_map_free(intrusive_hash_map *hash_map,
+                             void (*free_object)(void *));
+
+#endif /* GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_H */
diff --git a/src/core/ext/census/intrusive_hash_map_internal.h b/src/core/ext/census/intrusive_hash_map_internal.h
new file mode 100644
index 0000000..76a9a3a
--- /dev/null
+++ b/src/core/ext/census/intrusive_hash_map_internal.h
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright 2017, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_INTERNAL_H
+#define GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_INTERNAL_H
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/useful.h>
+#include <stdbool.h>
+
+/* The chunked vector is a data structure that allocates buckets for use in the
+ * hash map. ChunkedVector is logically equivalent to T*[N] (cast void* as
+ * T*). It's internally implemented as an array of 1MB arrays to avoid
+ * allocating large consecutive memory chunks. This is an internal data
+ * structure that should never be accessed directly. */
+typedef struct chunked_vector {
+  size_t size_;
+  void **first_;
+  void ***rest_;
+} chunked_vector;
+
+/* Core intrusive hash map data structure. All internal elements are managed by
+ * functions and should not be altered manually. */
+typedef struct intrusive_hash_map {
+  uint32_t num_items;
+  uint32_t extend_threshold;
+  uint32_t log2_num_buckets;
+  uint32_t hash_mask;
+  chunked_vector buckets;
+} intrusive_hash_map;
+
+#endif /* GRPC_CORE_EXT_CENSUS_INTRUSIVE_HASH_MAP_INTERNAL_H */
diff --git a/src/core/ext/filters/client_channel/channel_connectivity.c b/src/core/ext/filters/client_channel/channel_connectivity.c
index f83670d..04666ed 100644
--- a/src/core/ext/filters/client_channel/channel_connectivity.c
+++ b/src/core/ext/filters/client_channel/channel_connectivity.c
@@ -67,9 +67,8 @@
 
 typedef enum {
   WAITING,
-  CALLING_BACK,
+  READY_TO_CALL_BACK,
   CALLING_BACK_AND_FINISHED,
-  CALLED_BACK
 } callback_phase;
 
 typedef struct {
@@ -77,11 +76,13 @@
   callback_phase phase;
   grpc_closure on_complete;
   grpc_closure on_timeout;
+  grpc_closure watcher_timer_init;
   grpc_timer alarm;
   grpc_connectivity_state state;
   grpc_completion_queue *cq;
   grpc_cq_completion completion_storage;
   grpc_channel *channel;
+  grpc_error *error;
   void *tag;
 } state_watcher;
 
@@ -105,11 +106,8 @@
   gpr_mu_lock(&w->mu);
   switch (w->phase) {
     case WAITING:
-    case CALLED_BACK:
+    case READY_TO_CALL_BACK:
       GPR_UNREACHABLE_CODE(return );
-    case CALLING_BACK:
-      w->phase = CALLED_BACK;
-      break;
     case CALLING_BACK_AND_FINISHED:
       delete = 1;
       break;
@@ -123,10 +121,14 @@
 
 static void partly_done(grpc_exec_ctx *exec_ctx, state_watcher *w,
                         bool due_to_completion, grpc_error *error) {
-  int delete = 0;
-
   if (due_to_completion) {
     grpc_timer_cancel(exec_ctx, &w->alarm);
+  } else {
+    grpc_channel_element *client_channel_elem = grpc_channel_stack_last_element(
+        grpc_channel_get_channel_stack(w->channel));
+    grpc_client_channel_watch_connectivity_state(exec_ctx, client_channel_elem,
+                                                 grpc_cq_pollset(w->cq), NULL,
+                                                 &w->on_complete, NULL);
   }
 
   gpr_mu_lock(&w->mu);
@@ -147,25 +149,27 @@
   }
   switch (w->phase) {
     case WAITING:
-      w->phase = CALLING_BACK;
-      grpc_cq_end_op(exec_ctx, w->cq, w->tag, GRPC_ERROR_REF(error),
-                     finished_completion, w, &w->completion_storage);
+      GRPC_ERROR_REF(error);
+      w->error = error;
+      w->phase = READY_TO_CALL_BACK;
       break;
-    case CALLING_BACK:
+    case READY_TO_CALL_BACK:
+      if (error != GRPC_ERROR_NONE) {
+        GPR_ASSERT(!due_to_completion);
+        GRPC_ERROR_UNREF(w->error);
+        GRPC_ERROR_REF(error);
+        w->error = error;
+      }
       w->phase = CALLING_BACK_AND_FINISHED;
+      grpc_cq_end_op(exec_ctx, w->cq, w->tag, w->error, finished_completion, w,
+                     &w->completion_storage);
       break;
     case CALLING_BACK_AND_FINISHED:
       GPR_UNREACHABLE_CODE(return );
-    case CALLED_BACK:
-      delete = 1;
       break;
   }
   gpr_mu_unlock(&w->mu);
 
-  if (delete) {
-    delete_state_watcher(exec_ctx, w);
-  }
-
   GRPC_ERROR_UNREF(error);
 }
 
@@ -179,6 +183,28 @@
   partly_done(exec_ctx, pw, false, GRPC_ERROR_REF(error));
 }
 
+int grpc_channel_num_external_connectivity_watchers(grpc_channel *channel) {
+  grpc_channel_element *client_channel_elem =
+      grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel));
+  return grpc_client_channel_num_external_connectivity_watchers(
+      client_channel_elem);
+}
+
+typedef struct watcher_timer_init_arg {
+  state_watcher *w;
+  gpr_timespec deadline;
+} watcher_timer_init_arg;
+
+static void watcher_timer_init(grpc_exec_ctx *exec_ctx, void *arg,
+                               grpc_error *error_ignored) {
+  watcher_timer_init_arg *wa = (watcher_timer_init_arg *)arg;
+
+  grpc_timer_init(exec_ctx, &wa->w->alarm,
+                  gpr_convert_clock_type(wa->deadline, GPR_CLOCK_MONOTONIC),
+                  &wa->w->on_timeout, gpr_now(GPR_CLOCK_MONOTONIC));
+  gpr_free(wa);
+}
+
 void grpc_channel_watch_connectivity_state(
     grpc_channel *channel, grpc_connectivity_state last_observed_state,
     gpr_timespec deadline, grpc_completion_queue *cq, void *tag) {
@@ -208,16 +234,19 @@
   w->cq = cq;
   w->tag = tag;
   w->channel = channel;
+  w->error = NULL;
 
-  grpc_timer_init(&exec_ctx, &w->alarm,
-                  gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC),
-                  &w->on_timeout, gpr_now(GPR_CLOCK_MONOTONIC));
+  watcher_timer_init_arg *wa = gpr_malloc(sizeof(watcher_timer_init_arg));
+  wa->w = w;
+  wa->deadline = deadline;
+  grpc_closure_init(&w->watcher_timer_init, watcher_timer_init, wa,
+                    grpc_schedule_on_exec_ctx);
 
   if (client_channel_elem->filter == &grpc_client_channel_filter) {
     GRPC_CHANNEL_INTERNAL_REF(channel, "watch_channel_connectivity");
-    grpc_client_channel_watch_connectivity_state(&exec_ctx, client_channel_elem,
-                                                 grpc_cq_pollset(cq), &w->state,
-                                                 &w->on_complete);
+    grpc_client_channel_watch_connectivity_state(
+        &exec_ctx, client_channel_elem, grpc_cq_pollset(cq), &w->state,
+        &w->on_complete, &w->watcher_timer_init);
   } else {
     abort();
   }
diff --git a/src/core/ext/filters/client_channel/client_channel.c b/src/core/ext/filters/client_channel/client_channel.c
index f2f27b9..8cebbe9 100644
--- a/src/core/ext/filters/client_channel/client_channel.c
+++ b/src/core/ext/filters/client_channel/client_channel.c
@@ -167,6 +167,8 @@
   return value;
 }
 
+struct external_connectivity_watcher;
+
 /*************************************************************************
  * CHANNEL-WIDE FUNCTIONS
  */
@@ -204,6 +206,11 @@
   /** interested parties (owned) */
   grpc_pollset_set *interested_parties;
 
+  /* external_connectivity_watcher_list head is guarded by its own mutex, since
+   * counts need to be grabbed immediately without polling on a cq */
+  gpr_mu external_connectivity_watcher_list_mu;
+  struct external_connectivity_watcher *external_connectivity_watcher_list_head;
+
   /* the following properties are guarded by a mutex since API's require them
      to be instantaneously available */
   gpr_mu info_mu;
@@ -661,6 +668,12 @@
   // Initialize data members.
   chand->combiner = grpc_combiner_create(NULL);
   gpr_mu_init(&chand->info_mu);
+  gpr_mu_init(&chand->external_connectivity_watcher_list_mu);
+
+  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
+  chand->external_connectivity_watcher_list_head = NULL;
+  gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
+
   chand->owning_stack = args->channel_stack;
   grpc_closure_init(&chand->on_resolver_result_changed,
                     on_resolver_result_changed_locked, chand,
@@ -749,6 +762,7 @@
   grpc_pollset_set_destroy(exec_ctx, chand->interested_parties);
   GRPC_COMBINER_UNREF(exec_ctx, chand->combiner, "client_channel");
   gpr_mu_destroy(&chand->info_mu);
+  gpr_mu_destroy(&chand->external_connectivity_watcher_list_mu);
 }
 
 /*************************************************************************
@@ -1431,14 +1445,79 @@
   return out;
 }
 
-typedef struct {
+typedef struct external_connectivity_watcher {
   channel_data *chand;
   grpc_pollset *pollset;
   grpc_closure *on_complete;
+  grpc_closure *watcher_timer_init;
   grpc_connectivity_state *state;
   grpc_closure my_closure;
+  struct external_connectivity_watcher *next;
 } external_connectivity_watcher;
 
+static external_connectivity_watcher *lookup_external_connectivity_watcher(
+    channel_data *chand, grpc_closure *on_complete) {
+  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
+  external_connectivity_watcher *w =
+      chand->external_connectivity_watcher_list_head;
+  while (w != NULL && w->on_complete != on_complete) {
+    w = w->next;
+  }
+  gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
+  return w;
+}
+
+static void external_connectivity_watcher_list_append(
+    channel_data *chand, external_connectivity_watcher *w) {
+  GPR_ASSERT(!lookup_external_connectivity_watcher(chand, w->on_complete));
+
+  gpr_mu_lock(&w->chand->external_connectivity_watcher_list_mu);
+  GPR_ASSERT(!w->next);
+  w->next = chand->external_connectivity_watcher_list_head;
+  chand->external_connectivity_watcher_list_head = w;
+  gpr_mu_unlock(&w->chand->external_connectivity_watcher_list_mu);
+}
+
+static void external_connectivity_watcher_list_remove(
+    channel_data *chand, external_connectivity_watcher *too_remove) {
+  GPR_ASSERT(
+      lookup_external_connectivity_watcher(chand, too_remove->on_complete));
+  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
+  if (too_remove == chand->external_connectivity_watcher_list_head) {
+    chand->external_connectivity_watcher_list_head = too_remove->next;
+    gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
+    return;
+  }
+  external_connectivity_watcher *w =
+      chand->external_connectivity_watcher_list_head;
+  while (w != NULL) {
+    if (w->next == too_remove) {
+      w->next = w->next->next;
+      gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
+      return;
+    }
+    w = w->next;
+  }
+  GPR_UNREACHABLE_CODE(return );
+}
+
+int grpc_client_channel_num_external_connectivity_watchers(
+    grpc_channel_element *elem) {
+  channel_data *chand = elem->channel_data;
+  int count = 0;
+
+  gpr_mu_lock(&chand->external_connectivity_watcher_list_mu);
+  external_connectivity_watcher *w =
+      chand->external_connectivity_watcher_list_head;
+  while (w != NULL) {
+    count++;
+    w = w->next;
+  }
+  gpr_mu_unlock(&chand->external_connectivity_watcher_list_mu);
+
+  return count;
+}
+
 static void on_external_watch_complete(grpc_exec_ctx *exec_ctx, void *arg,
                                        grpc_error *error) {
   external_connectivity_watcher *w = arg;
@@ -1447,6 +1526,7 @@
                                w->pollset);
   GRPC_CHANNEL_STACK_UNREF(exec_ctx, w->chand->owning_stack,
                            "external_connectivity_watcher");
+  external_connectivity_watcher_list_remove(w->chand, w);
   gpr_free(w);
   grpc_closure_run(exec_ctx, follow_up, GRPC_ERROR_REF(error));
 }
@@ -1454,21 +1534,42 @@
 static void watch_connectivity_state_locked(grpc_exec_ctx *exec_ctx, void *arg,
                                             grpc_error *error_ignored) {
   external_connectivity_watcher *w = arg;
-  grpc_closure_init(&w->my_closure, on_external_watch_complete, w,
-                    grpc_schedule_on_exec_ctx);
-  grpc_connectivity_state_notify_on_state_change(
-      exec_ctx, &w->chand->state_tracker, w->state, &w->my_closure);
+  external_connectivity_watcher *found = NULL;
+  if (w->state != NULL) {
+    external_connectivity_watcher_list_append(w->chand, w);
+    grpc_closure_run(exec_ctx, w->watcher_timer_init, GRPC_ERROR_NONE);
+    grpc_closure_init(&w->my_closure, on_external_watch_complete, w,
+                      grpc_schedule_on_exec_ctx);
+    grpc_connectivity_state_notify_on_state_change(
+        exec_ctx, &w->chand->state_tracker, w->state, &w->my_closure);
+  } else {
+    GPR_ASSERT(w->watcher_timer_init == NULL);
+    found = lookup_external_connectivity_watcher(w->chand, w->on_complete);
+    if (found) {
+      GPR_ASSERT(found->on_complete == w->on_complete);
+      grpc_connectivity_state_notify_on_state_change(
+          exec_ctx, &found->chand->state_tracker, NULL, &found->my_closure);
+    }
+    grpc_pollset_set_del_pollset(exec_ctx, w->chand->interested_parties,
+                                 w->pollset);
+    GRPC_CHANNEL_STACK_UNREF(exec_ctx, w->chand->owning_stack,
+                             "external_connectivity_watcher");
+    gpr_free(w);
+  }
 }
 
 void grpc_client_channel_watch_connectivity_state(
     grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, grpc_pollset *pollset,
-    grpc_connectivity_state *state, grpc_closure *closure) {
+    grpc_connectivity_state *state, grpc_closure *closure,
+    grpc_closure *watcher_timer_init) {
   channel_data *chand = elem->channel_data;
-  external_connectivity_watcher *w = gpr_malloc(sizeof(*w));
+  external_connectivity_watcher *w = gpr_zalloc(sizeof(*w));
   w->chand = chand;
   w->pollset = pollset;
   w->on_complete = closure;
   w->state = state;
+  w->watcher_timer_init = watcher_timer_init;
+
   grpc_pollset_set_add_pollset(exec_ctx, chand->interested_parties, pollset);
   GRPC_CHANNEL_STACK_REF(w->chand->owning_stack,
                          "external_connectivity_watcher");
diff --git a/src/core/ext/filters/client_channel/client_channel.h b/src/core/ext/filters/client_channel/client_channel.h
index 8d2490e..356a7ab 100644
--- a/src/core/ext/filters/client_channel/client_channel.h
+++ b/src/core/ext/filters/client_channel/client_channel.h
@@ -53,9 +53,13 @@
 grpc_connectivity_state grpc_client_channel_check_connectivity_state(
     grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, int try_to_connect);
 
+int grpc_client_channel_num_external_connectivity_watchers(
+    grpc_channel_element *elem);
+
 void grpc_client_channel_watch_connectivity_state(
     grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, grpc_pollset *pollset,
-    grpc_connectivity_state *state, grpc_closure *on_complete);
+    grpc_connectivity_state *state, grpc_closure *on_complete,
+    grpc_closure *watcher_timer_init);
 
 /* Debug helper: pull the subchannel call from a call stack element */
 grpc_subchannel_call *grpc_client_channel_get_subchannel_call(
diff --git a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.c b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.c
index 2acde70..7ee6ffb 100644
--- a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.c
+++ b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.c
@@ -165,20 +165,21 @@
   if (GRPC_TRACER_ON(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_INFO,
             "[RR: %p] getting next ready subchannel, "
-            "last_ready_subchannel_index=%zu",
-            p, p->last_ready_subchannel_index);
+            "last_ready_subchannel_index=%lu",
+            p, (unsigned long)p->last_ready_subchannel_index);
   }
   for (size_t i = 0; i < p->num_subchannels; ++i) {
     const size_t index =
         (i + p->last_ready_subchannel_index + 1) % p->num_subchannels;
     if (GRPC_TRACER_ON(grpc_lb_round_robin_trace)) {
-      gpr_log(GPR_DEBUG, "[RR %p] checking index %zu: state=%d", p, index,
+      gpr_log(GPR_DEBUG, "[RR %p] checking index %lu: state=%d", p,
+              (unsigned long)index,
               p->subchannels[index].curr_connectivity_state);
     }
     if (p->subchannels[index].curr_connectivity_state == GRPC_CHANNEL_READY) {
       if (GRPC_TRACER_ON(grpc_lb_round_robin_trace)) {
-        gpr_log(GPR_DEBUG, "[RR %p] found next ready subchannel at index %zu",
-                p, index);
+        gpr_log(GPR_DEBUG, "[RR %p] found next ready subchannel at index %lu",
+                p, (unsigned long)index);
       }
       return index;
     }
@@ -196,8 +197,8 @@
   p->last_ready_subchannel_index = last_ready_index;
   if (GRPC_TRACER_ON(grpc_lb_round_robin_trace)) {
     gpr_log(GPR_DEBUG,
-            "[RR: %p] setting last_ready_subchannel_index=%zu (SC %p, CSC %p)",
-            (void *)p, last_ready_index,
+            "[RR: %p] setting last_ready_subchannel_index=%lu (SC %p, CSC %p)",
+            (void *)p, (unsigned long)last_ready_index,
             (void *)p->subchannels[last_ready_index].subchannel,
             (void *)grpc_subchannel_get_connected_subchannel(
                 p->subchannels[last_ready_index].subchannel));
@@ -342,8 +343,8 @@
     }
     if (GRPC_TRACER_ON(grpc_lb_round_robin_trace)) {
       gpr_log(GPR_DEBUG,
-              "[RR PICK] TARGET <-- CONNECTED SUBCHANNEL %p (INDEX %zu)",
-              (void *)*target, next_ready_index);
+              "[RR PICK] TARGET <-- CONNECTED SUBCHANNEL %p (INDEX %lu)",
+              (void *)*target, (unsigned long)next_ready_index);
     }
     /* only advance the last picked pointer if the selection was used */
     update_last_ready_subchannel_index_locked(p, next_ready_index);
@@ -510,8 +511,9 @@
         }
         if (GRPC_TRACER_ON(grpc_lb_round_robin_trace)) {
           gpr_log(GPR_DEBUG,
-                  "[RR CONN CHANGED] TARGET <-- SUBCHANNEL %p (INDEX %zu)",
-                  (void *)selected->subchannel, next_ready_index);
+                  "[RR CONN CHANGED] TARGET <-- SUBCHANNEL %p (INDEX %lu)",
+                  (void *)selected->subchannel,
+                  (unsigned long)next_ready_index);
         }
         grpc_closure_sched(exec_ctx, pp->on_complete, GRPC_ERROR_NONE);
         gpr_free(pp);
@@ -616,8 +618,8 @@
     if (GRPC_TRACER_ON(grpc_lb_round_robin_trace)) {
       char *address_uri =
           grpc_sockaddr_to_uri(&addresses->addresses[i].address);
-      gpr_log(GPR_DEBUG, "index %zu: Created subchannel %p for address uri %s",
-              subchannel_index, (void *)subchannel, address_uri);
+      gpr_log(GPR_DEBUG, "index %lu: Created subchannel %p for address uri %s",
+              (unsigned long)subchannel_index, (void *)subchannel, address_uri);
       gpr_free(address_uri);
     }
     grpc_channel_args_destroy(exec_ctx, new_args);
diff --git a/src/core/ext/transport/chttp2/server/chttp2_server.c b/src/core/ext/transport/chttp2/server/chttp2_server.c
index b9c62c3..2c076e8 100644
--- a/src/core/ext/transport/chttp2/server/chttp2_server.c
+++ b/src/core/ext/transport/chttp2/server/chttp2_server.c
@@ -127,6 +127,7 @@
   gpr_mu_lock(&state->mu);
   if (state->shutdown) {
     gpr_mu_unlock(&state->mu);
+    grpc_endpoint_shutdown(exec_ctx, tcp, GRPC_ERROR_NONE);
     grpc_endpoint_destroy(exec_ctx, tcp);
     gpr_free(acceptor);
     return;
diff --git a/src/core/lib/http/httpcli.c b/src/core/lib/http/httpcli.c
index 0ac2c2a..7012ffe 100644
--- a/src/core/lib/http/httpcli.c
+++ b/src/core/lib/http/httpcli.c
@@ -105,7 +105,7 @@
                    grpc_error *error) {
   grpc_polling_entity_del_from_pollset_set(exec_ctx, req->pollent,
                                            req->context->pollset_set);
-  grpc_closure_sched(exec_ctx, req->on_done, GRPC_ERROR_REF(error));
+  grpc_closure_sched(exec_ctx, req->on_done, error);
   grpc_http_parser_destroy(&req->parser);
   if (req->addresses != NULL) {
     grpc_resolved_addresses_destroy(req->addresses);
@@ -244,7 +244,7 @@
 static void on_resolved(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
   internal_request *req = arg;
   if (error != GRPC_ERROR_NONE) {
-    finish(exec_ctx, req, error);
+    finish(exec_ctx, req, GRPC_ERROR_REF(error));
     return;
   }
   req->next_address = 0;
diff --git a/src/core/lib/http/httpcli_security_connector.c b/src/core/lib/http/httpcli_security_connector.c
index 7694643..ea7c112 100644
--- a/src/core/lib/http/httpcli_security_connector.c
+++ b/src/core/lib/http/httpcli_security_connector.c
@@ -44,6 +44,7 @@
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/support/string.h"
 #include "src/core/tsi/ssl_transport_security.h"
+#include "src/core/tsi/transport_security_adapter.h"
 
 typedef struct {
   grpc_channel_security_connector base;
@@ -78,7 +79,8 @@
   }
   grpc_handshake_manager_add(
       handshake_mgr,
-      grpc_security_handshaker_create(exec_ctx, handshaker, &sc->base));
+      grpc_security_handshaker_create(
+          exec_ctx, tsi_create_adapter_handshaker(handshaker), &sc->base));
 }
 
 static void httpcli_ssl_check_peer(grpc_exec_ctx *exec_ctx,
diff --git a/src/core/lib/security/transport/security_connector.c b/src/core/lib/security/transport/security_connector.c
index 30431a4..416a3bd 100644
--- a/src/core/lib/security/transport/security_connector.c
+++ b/src/core/lib/security/transport/security_connector.c
@@ -56,6 +56,7 @@
 #include "src/core/lib/support/string.h"
 #include "src/core/tsi/fake_transport_security.h"
 #include "src/core/tsi/ssl_transport_security.h"
+#include "src/core/tsi/transport_security_adapter.h"
 
 /* -- Constants. -- */
 
@@ -390,7 +391,8 @@
   grpc_handshake_manager_add(
       handshake_mgr,
       grpc_security_handshaker_create(
-          exec_ctx, tsi_create_fake_handshaker(true /* is_client */),
+          exec_ctx, tsi_create_adapter_handshaker(
+                        tsi_create_fake_handshaker(true /* is_client */)),
           &sc->base));
 }
 
@@ -400,7 +402,8 @@
   grpc_handshake_manager_add(
       handshake_mgr,
       grpc_security_handshaker_create(
-          exec_ctx, tsi_create_fake_handshaker(false /* is_client */),
+          exec_ctx, tsi_create_adapter_handshaker(
+                        tsi_create_fake_handshaker(false /* is_client */)),
           &sc->base));
 }
 
@@ -495,8 +498,10 @@
   }
 
   // Create handshakers.
-  grpc_handshake_manager_add(handshake_mgr, grpc_security_handshaker_create(
-                                                exec_ctx, tsi_hs, &sc->base));
+  grpc_handshake_manager_add(
+      handshake_mgr,
+      grpc_security_handshaker_create(
+          exec_ctx, tsi_create_adapter_handshaker(tsi_hs), &sc->base));
 }
 
 static void ssl_server_add_handshakers(grpc_exec_ctx *exec_ctx,
@@ -515,8 +520,10 @@
   }
 
   // Create handshakers.
-  grpc_handshake_manager_add(handshake_mgr, grpc_security_handshaker_create(
-                                                exec_ctx, tsi_hs, &sc->base));
+  grpc_handshake_manager_add(
+      handshake_mgr,
+      grpc_security_handshaker_create(
+          exec_ctx, tsi_create_adapter_handshaker(tsi_hs), &sc->base));
 }
 
 static int ssl_host_matches_name(const tsi_peer *peer, const char *peer_name) {
diff --git a/src/core/lib/security/transport/security_handshaker.c b/src/core/lib/security/transport/security_handshaker.c
index 509b4b5..3bc113e 100644
--- a/src/core/lib/security/transport/security_handshaker.c
+++ b/src/core/lib/security/transport/security_handshaker.c
@@ -71,12 +71,12 @@
 
   unsigned char *handshake_buffer;
   size_t handshake_buffer_size;
-  grpc_slice_buffer left_overs;
   grpc_slice_buffer outgoing;
   grpc_closure on_handshake_data_sent_to_peer;
   grpc_closure on_handshake_data_received_from_peer;
   grpc_closure on_peer_checked;
   grpc_auth_context *auth_context;
+  tsi_handshaker_result *handshaker_result;
 } security_handshaker;
 
 static void security_handshaker_unref(grpc_exec_ctx *exec_ctx,
@@ -84,6 +84,7 @@
   if (gpr_unref(&h->refs)) {
     gpr_mu_destroy(&h->mu);
     tsi_handshaker_destroy(h->handshaker);
+    tsi_handshaker_result_destroy(h->handshaker_result);
     if (h->endpoint_to_destroy != NULL) {
       grpc_endpoint_destroy(exec_ctx, h->endpoint_to_destroy);
     }
@@ -92,7 +93,6 @@
       gpr_free(h->read_buffer_to_destroy);
     }
     gpr_free(h->handshake_buffer);
-    grpc_slice_buffer_destroy_internal(exec_ctx, &h->left_overs);
     grpc_slice_buffer_destroy_internal(exec_ctx, &h->outgoing);
     GRPC_AUTH_CONTEXT_UNREF(h->auth_context, "handshake");
     GRPC_SECURITY_CONNECTOR_UNREF(exec_ctx, h->connector, "handshake");
@@ -150,10 +150,10 @@
     security_handshake_failed_locked(exec_ctx, h, GRPC_ERROR_REF(error));
     goto done;
   }
-  // Get frame protector.
+  // Create frame protector.
   tsi_frame_protector *protector;
-  tsi_result result =
-      tsi_handshaker_create_frame_protector(h->handshaker, NULL, &protector);
+  tsi_result result = tsi_handshaker_result_create_frame_protector(
+      h->handshaker_result, NULL, &protector);
   if (result != TSI_OK) {
     error = grpc_set_tsi_error_result(
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Frame protector creation failed"),
@@ -161,14 +161,25 @@
     security_handshake_failed_locked(exec_ctx, h, error);
     goto done;
   }
-  // Success.
+  // Get unused bytes.
+  unsigned char *unused_bytes = NULL;
+  size_t unused_bytes_size = 0;
+  result = tsi_handshaker_result_get_unused_bytes(
+      h->handshaker_result, &unused_bytes, &unused_bytes_size);
   // Create secure endpoint.
-  h->args->endpoint = grpc_secure_endpoint_create(
-      protector, h->args->endpoint, h->left_overs.slices, h->left_overs.count);
-  h->left_overs.count = 0;
-  h->left_overs.length = 0;
-  // Clear out the read buffer before it gets passed to the transport,
-  // since any excess bytes were already copied to h->left_overs.
+  if (unused_bytes_size > 0) {
+    grpc_slice slice =
+        grpc_slice_from_copied_buffer((char *)unused_bytes, unused_bytes_size);
+    h->args->endpoint =
+        grpc_secure_endpoint_create(protector, h->args->endpoint, &slice, 1);
+    grpc_slice_unref_internal(exec_ctx, slice);
+  } else {
+    h->args->endpoint =
+        grpc_secure_endpoint_create(protector, h->args->endpoint, NULL, 0);
+  }
+  tsi_handshaker_result_destroy(h->handshaker_result);
+  h->handshaker_result = NULL;
+  // Clear out the read buffer before it gets passed to the transport.
   grpc_slice_buffer_reset_and_unref_internal(exec_ctx, h->args->read_buffer);
   // Add auth context to channel args.
   grpc_arg auth_context_arg = grpc_auth_context_to_arg(h->auth_context);
@@ -189,7 +200,8 @@
 static grpc_error *check_peer_locked(grpc_exec_ctx *exec_ctx,
                                      security_handshaker *h) {
   tsi_peer peer;
-  tsi_result result = tsi_handshaker_extract_peer(h->handshaker, &peer);
+  tsi_result result =
+      tsi_handshaker_result_extract_peer(h->handshaker_result, &peer);
   if (result != TSI_OK) {
     return grpc_set_tsi_error_result(
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Peer extraction failed"), result);
@@ -199,34 +211,87 @@
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *send_handshake_bytes_to_peer_locked(grpc_exec_ctx *exec_ctx,
-                                                       security_handshaker *h) {
-  // Get data to send.
-  tsi_result result = TSI_OK;
-  size_t offset = 0;
-  do {
-    size_t to_send_size = h->handshake_buffer_size - offset;
-    result = tsi_handshaker_get_bytes_to_send_to_peer(
-        h->handshaker, h->handshake_buffer + offset, &to_send_size);
-    offset += to_send_size;
-    if (result == TSI_INCOMPLETE_DATA) {
-      h->handshake_buffer_size *= 2;
-      h->handshake_buffer =
-          gpr_realloc(h->handshake_buffer, h->handshake_buffer_size);
-    }
-  } while (result == TSI_INCOMPLETE_DATA);
+static grpc_error *on_handshake_next_done_locked(
+    grpc_exec_ctx *exec_ctx, security_handshaker *h, tsi_result result,
+    const unsigned char *bytes_to_send, size_t bytes_to_send_size,
+    tsi_handshaker_result *handshaker_result) {
+  grpc_error *error = GRPC_ERROR_NONE;
+  // Read more if we need to.
+  if (result == TSI_INCOMPLETE_DATA) {
+    GPR_ASSERT(bytes_to_send_size == 0);
+    grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
+                       &h->on_handshake_data_received_from_peer);
+    return error;
+  }
   if (result != TSI_OK) {
     return grpc_set_tsi_error_result(
         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Handshake failed"), result);
   }
-  // Send data.
-  grpc_slice to_send =
-      grpc_slice_from_copied_buffer((const char *)h->handshake_buffer, offset);
-  grpc_slice_buffer_reset_and_unref_internal(exec_ctx, &h->outgoing);
-  grpc_slice_buffer_add(&h->outgoing, to_send);
-  grpc_endpoint_write(exec_ctx, h->args->endpoint, &h->outgoing,
-                      &h->on_handshake_data_sent_to_peer);
-  return GRPC_ERROR_NONE;
+  // Update handshaker result.
+  if (handshaker_result != NULL) {
+    GPR_ASSERT(h->handshaker_result == NULL);
+    h->handshaker_result = handshaker_result;
+  }
+  if (bytes_to_send_size > 0) {
+    // Send data to peer, if needed.
+    grpc_slice to_send = grpc_slice_from_copied_buffer(
+        (const char *)bytes_to_send, bytes_to_send_size);
+    grpc_slice_buffer_reset_and_unref_internal(exec_ctx, &h->outgoing);
+    grpc_slice_buffer_add(&h->outgoing, to_send);
+    grpc_endpoint_write(exec_ctx, h->args->endpoint, &h->outgoing,
+                        &h->on_handshake_data_sent_to_peer);
+  } else if (handshaker_result == NULL) {
+    // There is nothing to send, but need to read from peer.
+    grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
+                       &h->on_handshake_data_received_from_peer);
+  } else {
+    // Handshake has finished, check peer and so on.
+    error = check_peer_locked(exec_ctx, h);
+  }
+  return error;
+}
+
+static void on_handshake_next_done_grpc_wrapper(
+    tsi_result result, void *user_data, const unsigned char *bytes_to_send,
+    size_t bytes_to_send_size, tsi_handshaker_result *handshaker_result) {
+  security_handshaker *h = user_data;
+  // This callback will be invoked by TSI in a non-grpc thread, so it's
+  // safe to create our own exec_ctx here.
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  gpr_mu_lock(&h->mu);
+  grpc_error *error =
+      on_handshake_next_done_locked(&exec_ctx, h, result, bytes_to_send,
+                                    bytes_to_send_size, handshaker_result);
+  if (error != GRPC_ERROR_NONE) {
+    security_handshake_failed_locked(&exec_ctx, h, error);
+    gpr_mu_unlock(&h->mu);
+    security_handshaker_unref(&exec_ctx, h);
+  } else {
+    gpr_mu_unlock(&h->mu);
+  }
+  grpc_exec_ctx_finish(&exec_ctx);
+}
+
+static grpc_error *do_handshaker_next_locked(
+    grpc_exec_ctx *exec_ctx, security_handshaker *h,
+    const unsigned char *bytes_received, size_t bytes_received_size) {
+  // Invoke TSI handshaker.
+  unsigned char *bytes_to_send = NULL;
+  size_t bytes_to_send_size = 0;
+  tsi_handshaker_result *handshaker_result = NULL;
+  tsi_result result = tsi_handshaker_next(
+      h->handshaker, bytes_received, bytes_received_size, &bytes_to_send,
+      &bytes_to_send_size, &handshaker_result,
+      &on_handshake_next_done_grpc_wrapper, h);
+  if (result == TSI_ASYNC) {
+    // Handshaker operating asynchronously. Nothing else to do here;
+    // callback will be invoked in a TSI thread.
+    return GRPC_ERROR_NONE;
+  }
+  // Handshaker returned synchronously. Invoke callback directly in
+  // this thread with our existing exec_ctx.
+  return on_handshake_next_done_locked(exec_ctx, h, result, bytes_to_send,
+                                       bytes_to_send_size, handshaker_result);
 }
 
 static void on_handshake_data_received_from_peer(grpc_exec_ctx *exec_ctx,
@@ -241,72 +306,34 @@
     security_handshaker_unref(exec_ctx, h);
     return;
   }
-  // Process received data.
-  tsi_result result = TSI_OK;
-  size_t consumed_slice_size = 0;
+  // Copy all slices received.
   size_t i;
+  size_t bytes_received_size = 0;
   for (i = 0; i < h->args->read_buffer->count; i++) {
-    consumed_slice_size = GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]);
-    result = tsi_handshaker_process_bytes_from_peer(
-        h->handshaker, GRPC_SLICE_START_PTR(h->args->read_buffer->slices[i]),
-        &consumed_slice_size);
-    if (!tsi_handshaker_is_in_progress(h->handshaker)) break;
+    bytes_received_size += GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]);
   }
-  if (tsi_handshaker_is_in_progress(h->handshaker)) {
-    /* We may need more data. */
-    if (result == TSI_INCOMPLETE_DATA) {
-      grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
-                         &h->on_handshake_data_received_from_peer);
-      goto done;
-    } else {
-      error = send_handshake_bytes_to_peer_locked(exec_ctx, h);
-      if (error != GRPC_ERROR_NONE) {
-        security_handshake_failed_locked(exec_ctx, h, error);
-        gpr_mu_unlock(&h->mu);
-        security_handshaker_unref(exec_ctx, h);
-        return;
-      }
-      goto done;
-    }
+  if (bytes_received_size > h->handshake_buffer_size) {
+    h->handshake_buffer = gpr_realloc(h->handshake_buffer, bytes_received_size);
+    h->handshake_buffer_size = bytes_received_size;
   }
-  if (result != TSI_OK) {
-    security_handshake_failed_locked(
-        exec_ctx, h,
-        grpc_set_tsi_error_result(
-            GRPC_ERROR_CREATE_FROM_STATIC_STRING("Handshake failed"), result));
-    gpr_mu_unlock(&h->mu);
-    security_handshaker_unref(exec_ctx, h);
-    return;
+  size_t offset = 0;
+  for (i = 0; i < h->args->read_buffer->count; i++) {
+    size_t slice_size = GPR_SLICE_LENGTH(h->args->read_buffer->slices[i]);
+    memcpy(h->handshake_buffer + offset,
+           GRPC_SLICE_START_PTR(h->args->read_buffer->slices[i]), slice_size);
+    offset += slice_size;
   }
-  /* Handshake is done and successful this point. */
-  bool has_left_overs_in_current_slice =
-      (consumed_slice_size <
-       GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]));
-  size_t num_left_overs = (has_left_overs_in_current_slice ? 1 : 0) +
-                          h->args->read_buffer->count - i - 1;
-  if (num_left_overs > 0) {
-    /* Put the leftovers in our buffer (ownership transfered). */
-    if (has_left_overs_in_current_slice) {
-      grpc_slice tail = grpc_slice_split_tail(&h->args->read_buffer->slices[i],
-                                              consumed_slice_size);
-      grpc_slice_buffer_add(&h->left_overs, tail);
-      /* split_tail above increments refcount. */
-      grpc_slice_unref_internal(exec_ctx, tail);
-    }
-    grpc_slice_buffer_addn(
-        &h->left_overs, &h->args->read_buffer->slices[i + 1],
-        num_left_overs - (size_t)has_left_overs_in_current_slice);
-  }
-  // Check peer.
-  error = check_peer_locked(exec_ctx, h);
+  // Call TSI handshaker.
+  error = do_handshaker_next_locked(exec_ctx, h, h->handshake_buffer,
+                                    bytes_received_size);
+
   if (error != GRPC_ERROR_NONE) {
     security_handshake_failed_locked(exec_ctx, h, error);
     gpr_mu_unlock(&h->mu);
     security_handshaker_unref(exec_ctx, h);
-    return;
+  } else {
+    gpr_mu_unlock(&h->mu);
   }
-done:
-  gpr_mu_unlock(&h->mu);
 }
 
 static void on_handshake_data_sent_to_peer(grpc_exec_ctx *exec_ctx, void *arg,
@@ -321,8 +348,8 @@
     security_handshaker_unref(exec_ctx, h);
     return;
   }
-  /* We may be done. */
-  if (tsi_handshaker_is_in_progress(h->handshaker)) {
+  // We may be done.
+  if (h->handshaker_result == NULL) {
     grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
                        &h->on_handshake_data_received_from_peer);
   } else {
@@ -371,7 +398,7 @@
   h->args = args;
   h->on_handshake_done = on_handshake_done;
   gpr_ref(&h->refs);
-  grpc_error *error = send_handshake_bytes_to_peer_locked(exec_ctx, h);
+  grpc_error *error = do_handshaker_next_locked(exec_ctx, h, NULL, 0);
   if (error != GRPC_ERROR_NONE) {
     security_handshake_failed_locked(exec_ctx, h, error);
     gpr_mu_unlock(&h->mu);
@@ -404,7 +431,6 @@
                     grpc_schedule_on_exec_ctx);
   grpc_closure_init(&h->on_peer_checked, on_peer_checked, h,
                     grpc_schedule_on_exec_ctx);
-  grpc_slice_buffer_init(&h->left_overs);
   grpc_slice_buffer_init(&h->outgoing);
   return &h->base;
 }
diff --git a/src/core/lib/surface/version.c b/src/core/lib/surface/version.c
index cddc595..39dfebb 100644
--- a/src/core/lib/surface/version.c
+++ b/src/core/lib/surface/version.c
@@ -36,6 +36,6 @@
 
 #include <grpc/grpc.h>
 
-const char *grpc_version_string(void) { return "4.0.0-dev"; }
+const char *grpc_version_string(void) { return "4.0.0-pre1"; }
 
 const char *grpc_g_stands_for(void) { return "gregarious"; }
diff --git a/src/cpp/common/version_cc.cc b/src/cpp/common/version_cc.cc
index 72a4c4c..8359016 100644
--- a/src/cpp/common/version_cc.cc
+++ b/src/cpp/common/version_cc.cc
@@ -37,5 +37,5 @@
 #include <grpc++/grpc++.h>
 
 namespace grpc {
-grpc::string Version() { return "1.4.0-dev"; }
+grpc::string Version() { return "1.4.0-pre1"; }
 }
diff --git a/src/cpp/server/server_builder.cc b/src/cpp/server/server_builder.cc
index 6dca6a6..c849d2e 100644
--- a/src/cpp/server/server_builder.cc
+++ b/src/cpp/server/server_builder.cc
@@ -180,6 +180,10 @@
 }
 
 std::unique_ptr<Server> ServerBuilder::BuildAndStart() {
+  for (auto plugin = plugins_.begin(); plugin != plugins_.end(); plugin++) {
+    (*plugin)->UpdateServerBuilder(this);
+  }
+
   ChannelArguments args;
   for (auto option = options_.begin(); option != options_.end(); ++option) {
     (*option)->UpdateArguments(&args);
diff --git a/src/csharp/.editorconfig b/src/csharp/.editorconfig
new file mode 100644
index 0000000..7bc2bcc
--- /dev/null
+++ b/src/csharp/.editorconfig
@@ -0,0 +1,7 @@
+root = true
+[**]
+end_of_line = LF
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+tab_width = 4
diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
index 6ac25aa..188ddb9 100755
--- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj
+++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
@@ -16,6 +16,8 @@
     <PackageProjectUrl>https://github.com/grpc/grpc</PackageProjectUrl>
     <PackageLicenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</PackageLicenseUrl>
     <NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.5' ">1.6.0</NetStandardImplicitPackageVersion>
+    <IncludeSymbols>true</IncludeSymbols>
+    <IncludeSource>true</IncludeSource>
   </PropertyGroup>
 
   <ItemGroup>
@@ -23,7 +25,9 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj" />
+    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj">
+      <PrivateAssets>None</PrivateAssets>
+    </ProjectReference>
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/csharp/Grpc.Core.Testing/Grpc.Core.Testing.csproj b/src/csharp/Grpc.Core.Testing/Grpc.Core.Testing.csproj
index f4dd510..45ec874 100755
--- a/src/csharp/Grpc.Core.Testing/Grpc.Core.Testing.csproj
+++ b/src/csharp/Grpc.Core.Testing/Grpc.Core.Testing.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <Import Project="..\Grpc.Core\Version.csproj.include" />
   <Import Project="..\Grpc.Core\Common.csproj.include" />
@@ -16,6 +16,8 @@
     <PackageProjectUrl>https://github.com/grpc/grpc</PackageProjectUrl>
     <PackageLicenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</PackageLicenseUrl>
     <NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.5' ">1.6.0</NetStandardImplicitPackageVersion>
+    <IncludeSymbols>true</IncludeSymbols>
+    <IncludeSource>true</IncludeSource>
   </PropertyGroup>
 
   <ItemGroup>
@@ -23,7 +25,9 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj" />
+    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj">
+      <PrivateAssets>None</PrivateAssets>
+    </ProjectReference>
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
diff --git a/src/csharp/Grpc.Core.Tests/ServerTest.cs b/src/csharp/Grpc.Core.Tests/ServerTest.cs
index 3b51aa6..5e95ed9 100644
--- a/src/csharp/Grpc.Core.Tests/ServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ServerTest.cs
@@ -32,6 +32,7 @@
 #endregion
 
 using System;
+using System.IO;
 using System.Linq;
 using Grpc.Core;
 using Grpc.Core.Internal;
@@ -81,6 +82,21 @@
         }
 
         [Test]
+        public void StartThrowsWithUnboundPorts()
+        {
+            int twiceBoundPort = 9999;
+            Server server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
+            {
+                Ports = {
+                    new ServerPort("localhost", twiceBoundPort, ServerCredentials.Insecure),
+                    new ServerPort("localhost", twiceBoundPort, ServerCredentials.Insecure)
+                }
+            };
+            Assert.Throws(typeof(IOException), () => server.Start());
+            server.ShutdownAsync().Wait();
+        }
+
+        [Test]
         public void CannotModifyAfterStarted()
         {
             Server server = new Server
diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs
index 4f29c35..51ae11f 100644
--- a/src/csharp/Grpc.Core/Channel.cs
+++ b/src/csharp/Grpc.Core/Channel.cs
@@ -59,6 +59,8 @@
         readonly ChannelSafeHandle handle;
         readonly Dictionary<string, ChannelOption> options;
 
+        readonly Task connectivityWatcherTask;
+
         bool shutdownRequested;
 
         /// <summary>
@@ -99,6 +101,9 @@
                     this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
                 }
             }
+            // TODO(jtattermusch): Workaround for https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/822.
+            // Remove once retries are supported in C core
+            this.connectivityWatcherTask = RunConnectivityWatcherAsync();
             GrpcEnvironment.RegisterChannel(this);
         }
 
@@ -244,7 +249,7 @@
 
             handle.Dispose();
 
-            await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false);
+            await Task.WhenAll(GrpcEnvironment.ReleaseAsync(), connectivityWatcherTask).ConfigureAwait(false);
         }
 
         internal ChannelSafeHandle Handle
@@ -299,6 +304,40 @@
             }
         }
 
+        /// <summary>
+        /// Constantly Watches channel connectivity status to work around https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/822
+        /// </summary>
+        private async Task RunConnectivityWatcherAsync()
+        {
+            try
+            {
+                var lastState = State;
+                while (lastState != ChannelState.Shutdown)
+                {
+                    lock (myLock)
+                    {
+                        if (shutdownRequested)
+                        {
+                            break;
+                        }
+                    }
+
+                    try
+                    {
+                        await WaitForStateChangedAsync(lastState, DateTime.UtcNow.AddSeconds(1)).ConfigureAwait(false);
+                    }
+                    catch (TaskCanceledException)
+                    {
+                        // ignore timeout
+                    }
+                    lastState = State;
+                }
+            }
+            catch (ObjectDisposedException) {
+                // during shutdown, channel is going to be disposed.
+            }
+        }
+
         private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options)
         {
             var key = ChannelOptions.PrimaryUserAgentString;
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index c086500..ae0d8b2 100755
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <Import Project="Version.csproj.include" />
   <Import Project="Common.csproj.include" />
@@ -15,6 +15,8 @@
     <PackageProjectUrl>https://github.com/grpc/grpc</PackageProjectUrl>
     <PackageLicenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</PackageLicenseUrl>
     <NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.5' ">1.6.0</NetStandardImplicitPackageVersion>
+    <IncludeSymbols>true</IncludeSymbols>
+    <IncludeSource>true</IncludeSource>
   </PropertyGroup>
 
   <ItemGroup>
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index 8ed0c0b..bc74e21 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -218,5 +218,16 @@
         {
             return buffered ? 0 : GRPC_WRITE_BUFFER_HINT;
         }
+
+        /// <summary>
+        /// Only for testing.
+        /// </summary>
+        public static CallSafeHandle CreateFake(IntPtr ptr, CompletionQueueSafeHandle cq)
+        {
+            var call = new CallSafeHandle();
+            call.SetHandle(ptr);
+            call.Initialize(cq);
+            return call;
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
index a4aa8d3..075286d 100644
--- a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
+++ b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
@@ -52,6 +52,7 @@
 
         readonly GrpcEnvironment environment;
         readonly ConcurrentDictionary<IntPtr, OpCompletionDelegate> dict = new ConcurrentDictionary<IntPtr, OpCompletionDelegate>(new IntPtrComparer());
+        IntPtr lastRegisteredKey;  // only for testing
 
         public CompletionRegistry(GrpcEnvironment environment)
         {
@@ -62,6 +63,7 @@
         {
             environment.DebugStats.PendingBatchCompletions.Increment();
             GrpcPreconditions.CheckState(dict.TryAdd(key, callback));
+            this.lastRegisteredKey = key;
         }
 
         public void RegisterBatchCompletion(BatchContextSafeHandle ctx, BatchCompletionDelegate callback)
@@ -84,6 +86,14 @@
             return value;
         }
 
+        /// <summary>
+        /// For testing purposes only.
+        /// </summary>
+        public IntPtr LastRegisteredKey
+        {
+            get { return this.lastRegisteredKey; }
+        }
+
         private static void HandleBatchCompletion(bool success, BatchContextSafeHandle ctx, BatchCompletionDelegate callback)
         {
             try
diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
index 696987d..e703e3e 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMethods.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMethods.cs
@@ -164,6 +164,8 @@
         public readonly Delegates.grpcsharp_test_callback_delegate grpcsharp_test_callback;
         public readonly Delegates.grpcsharp_test_nop_delegate grpcsharp_test_nop;
 
+        public readonly Delegates.grpcsharp_test_override_method_delegate grpcsharp_test_override_method;
+
         #endregion
 
         public NativeMethods(UnmanagedLibrary library)
@@ -278,6 +280,7 @@
 
             this.grpcsharp_test_callback = GetMethodDelegate<Delegates.grpcsharp_test_callback_delegate>(library);
             this.grpcsharp_test_nop = GetMethodDelegate<Delegates.grpcsharp_test_nop_delegate>(library);
+            this.grpcsharp_test_override_method = GetMethodDelegate<Delegates.grpcsharp_test_override_method_delegate>(library);
         }
 
         /// <summary>
@@ -434,6 +437,7 @@
 
             public delegate CallError grpcsharp_test_callback_delegate([MarshalAs(UnmanagedType.FunctionPtr)] OpCompletionDelegate 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/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs
index 77ac347..fe75782 100644
--- a/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs
+++ b/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs
@@ -59,8 +59,14 @@
     "0442bb8e12768722de0b0cb1b15e955b32a11352740ee59f2c94c48edc8e177d1052536b8ac651bce11ce5da3a" +
     "27fc95aff3dc604a6971417453f9483c7b5e836756d5b271bf8f2403fe186e31956148c03d804487cf642f8cc0" +
     "71394ee9672dfe5b55ea0f95dfd5a7f77d22c962ccf51320d3")]
+[assembly: InternalsVisibleTo("Grpc.Microbenchmarks,PublicKey=" +
+    "00240000048000009400000006020000002400005253413100040000010001002f5797a92c6fcde81bd4098f43" +
+    "0442bb8e12768722de0b0cb1b15e955b32a11352740ee59f2c94c48edc8e177d1052536b8ac651bce11ce5da3a" +
+    "27fc95aff3dc604a6971417453f9483c7b5e836756d5b271bf8f2403fe186e31956148c03d804487cf642f8cc0" +
+    "71394ee9672dfe5b55ea0f95dfd5a7f77d22c962ccf51320d3")]
 #else
 [assembly: InternalsVisibleTo("Grpc.Core.Tests")]
 [assembly: InternalsVisibleTo("Grpc.Core.Testing")]
 [assembly: InternalsVisibleTo("Grpc.IntegrationTesting")]
+[assembly: InternalsVisibleTo("Grpc.Microbenchmarks")]
 #endif
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index 63c1d9c..1fb5c62 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -34,6 +34,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using Grpc.Core.Internal;
@@ -155,6 +156,7 @@
 
         /// <summary>
         /// Starts the server.
+        /// Throws <c>IOException</c> if not successful.
         /// </summary>
         public void Start()
         {
@@ -163,7 +165,8 @@
                 GrpcPreconditions.CheckState(!startRequested);
                 GrpcPreconditions.CheckState(!shutdownRequested);
                 startRequested = true;
-                
+
+                CheckPortsBoundSuccessfully();
                 handle.Start();
 
                 for (int i = 0; i < requestCallTokensPerCq; i++)
@@ -316,6 +319,22 @@
             }
         }
 
+        /// <summary>
+        /// Checks that all ports have been bound successfully.
+        /// </summary>
+        private void CheckPortsBoundSuccessfully()
+        {
+            lock (myLock)
+            {
+                var unboundPort = ports.FirstOrDefault(port => port.BoundPort == 0);
+                if (unboundPort != null)
+                {
+                    throw new IOException(
+                        string.Format("Failed to bind port \"{0}:{1}\"", unboundPort.Host, unboundPort.Port));
+                }
+            }
+        }
+
         private void DisposeHandle()
         {
             var activeCallCount = activeCallCounter.Count;
diff --git a/src/csharp/Grpc.Core/Version.csproj.include b/src/csharp/Grpc.Core/Version.csproj.include
index 8388bfd..8a15980 100755
--- a/src/csharp/Grpc.Core/Version.csproj.include
+++ b/src/csharp/Grpc.Core/Version.csproj.include
@@ -1,7 +1,7 @@
 <!-- This file is generated -->
 <Project>
   <PropertyGroup>
-    <GrpcCsharpVersion>1.4.0-dev</GrpcCsharpVersion>
+    <GrpcCsharpVersion>1.4.0-pre1</GrpcCsharpVersion>
     <GoogleProtobufVersion>3.3.0</GoogleProtobufVersion>
   </PropertyGroup>
 </Project>
diff --git a/src/csharp/Grpc.Core/VersionInfo.cs b/src/csharp/Grpc.Core/VersionInfo.cs
index 2e55d9d..feac073 100644
--- a/src/csharp/Grpc.Core/VersionInfo.cs
+++ b/src/csharp/Grpc.Core/VersionInfo.cs
@@ -53,6 +53,6 @@
         /// <summary>
         /// Current version of gRPC C#
         /// </summary>
-        public const string CurrentVersion = "1.4.0-dev";
+        public const string CurrentVersion = "1.4.0-pre1";
     }
 }
diff --git a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj
index eac6e1f..c3791a4 100755
--- a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj
+++ b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj
@@ -15,6 +15,8 @@
     <PackageProjectUrl>https://github.com/grpc/grpc</PackageProjectUrl>
     <PackageLicenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</PackageLicenseUrl>
     <NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.5' ">1.6.0</NetStandardImplicitPackageVersion>
+    <IncludeSymbols>true</IncludeSymbols>
+    <IncludeSource>true</IncludeSource>
   </PropertyGroup>
 
   <ItemGroup>
@@ -22,7 +24,9 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj" />
+    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj">
+      <PrivateAssets>None</PrivateAssets>
+    </ProjectReference>
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/csharp/Grpc.Microbenchmarks/.gitignore b/src/csharp/Grpc.Microbenchmarks/.gitignore
new file mode 100644
index 0000000..1746e32
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/.gitignore
@@ -0,0 +1,2 @@
+bin
+obj
diff --git a/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj b/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
new file mode 100644
index 0000000..26a940e
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/Grpc.Microbenchmarks.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <Import Project="..\Grpc.Core\Version.csproj.include" />
+  <Import Project="..\Grpc.Core\Common.csproj.include" />
+
+  <PropertyGroup>
+    <TargetFrameworks>net45;netcoreapp1.0</TargetFrameworks>
+    <AssemblyName>Grpc.Microbenchmarks</AssemblyName>
+    <OutputType>Exe</OutputType>
+    <PackageId>Grpc.Microbenchmarks</PackageId>
+    <PackageTargetFallback Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">$(PackageTargetFallback);portable-net45</PackageTargetFallback>
+    <RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">1.0.4</RuntimeFrameworkVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
+    <Reference Include="System" />
+    <Reference Include="Microsoft.CSharp" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="..\Grpc.Core\Version.cs" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/csharp/Grpc.Microbenchmarks/Program.cs b/src/csharp/Grpc.Microbenchmarks/Program.cs
new file mode 100644
index 0000000..a0ca1f7
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/Program.cs
@@ -0,0 +1,55 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Logging;
+
+namespace Grpc.Microbenchmarks
+{
+    class Program
+    {
+        public static void Main(string[] args)
+        {
+            GrpcEnvironment.SetLogger(new TextWriterLogger(Console.Error));
+            var benchmark = new SendMessageBenchmark();
+            benchmark.Init();
+            foreach (int threadCount in new int[] {1, 1, 2, 4, 8, 12})
+            {
+                benchmark.Run(threadCount, 4 * 1000 * 1000, 0);
+            }
+            benchmark.Cleanup();
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Microbenchmarks/SendMessageBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/SendMessageBenchmark.cs
new file mode 100644
index 0000000..eea3758
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/SendMessageBenchmark.cs
@@ -0,0 +1,106 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Threading;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Grpc.Microbenchmarks
+{
+    public class SendMessageBenchmark
+    {
+        static readonly NativeMethods Native = NativeMethods.Get();
+
+        GrpcEnvironment environment;
+
+        public void Init()
+        {
+            Native.grpcsharp_test_override_method("grpcsharp_call_start_batch", "nop");
+            environment = GrpcEnvironment.AddRef();
+        }
+
+        public void Cleanup()
+        {
+            GrpcEnvironment.ReleaseAsync().Wait();
+            // TODO(jtattermusch): track GC stats
+        }
+
+        public void Run(int threadCount, int iterations, int payloadSize)
+        {
+            Console.WriteLine(string.Format("SendMessageBenchmark: 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)
+        {
+            // TODO(jtattermusch): parametrize by number of pending completions.
+            // TODO(jtattermusch): parametrize by cached/non-cached BatchContextSafeHandle
+
+            var completionRegistry = new CompletionRegistry(environment);
+            var cq = CompletionQueueSafeHandle.CreateAsync(completionRegistry);
+            var call = CreateFakeCall(cq);
+
+            var sendCompletionHandler = new SendCompletionHandler((success) => { });
+            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);
+                var callback = completionRegistry.Extract(completionRegistry.LastRegisteredKey);
+                callback(true);
+            }
+            stopwatch.Stop();
+            Console.WriteLine("Elapsed millis: " + stopwatch.ElapsedMilliseconds);
+
+            cq.Dispose();
+        }
+
+        private static CallSafeHandle CreateFakeCall(CompletionQueueSafeHandle cq)
+        {
+            var call = CallSafeHandle.CreateFake(new IntPtr(0xdead), cq);
+            bool success = false;
+            while (!success)
+            {
+                // avoid calling destroy on a nonexistent grpc_call pointer
+                call.DangerousAddRef(ref success);
+            }
+            return call;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Microbenchmarks/ThreadedBenchmark.cs b/src/csharp/Grpc.Microbenchmarks/ThreadedBenchmark.cs
new file mode 100644
index 0000000..1c54624
--- /dev/null
+++ b/src/csharp/Grpc.Microbenchmarks/ThreadedBenchmark.cs
@@ -0,0 +1,79 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Threading;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Grpc.Microbenchmarks
+{
+    public class ThreadedBenchmark
+    {
+        List<ThreadStart> runners;
+
+        public ThreadedBenchmark(IEnumerable<ThreadStart> runners)
+        {
+            this.runners = new List<ThreadStart>(runners);
+        }
+
+        public ThreadedBenchmark(int threadCount, Action threadBody)
+        {
+            this.runners = new List<ThreadStart>();
+            for (int i = 0; i < threadCount; i++)
+            {
+                this.runners.Add(new ThreadStart(() => threadBody()));
+            }
+        }
+        
+        public void Run()
+        {
+            Console.WriteLine("Running threads.");
+            var threads = new List<Thread>();
+            for (int i = 0; i < runners.Count; i++)
+            {
+                var thread = new Thread(runners[i]);
+                thread.Start();
+                threads.Add(thread);
+            }
+
+            foreach (var thread in threads)
+            {
+                thread.Join();
+            }
+            Console.WriteLine("All threads finished.");
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Reflection/Grpc.Reflection.csproj b/src/csharp/Grpc.Reflection/Grpc.Reflection.csproj
index 70bfcc8..3a07555 100755
--- a/src/csharp/Grpc.Reflection/Grpc.Reflection.csproj
+++ b/src/csharp/Grpc.Reflection/Grpc.Reflection.csproj
@@ -15,6 +15,8 @@
     <PackageProjectUrl>https://github.com/grpc/grpc</PackageProjectUrl>
     <PackageLicenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</PackageLicenseUrl>
     <NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.5' ">1.6.0</NetStandardImplicitPackageVersion>
+    <IncludeSymbols>true</IncludeSymbols>
+    <IncludeSource>true</IncludeSource>
   </PropertyGroup>
 
   <ItemGroup>
@@ -22,7 +24,9 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj" />
+    <ProjectReference Include="../Grpc.Core/Grpc.Core.csproj">
+      <PrivateAssets>None</PrivateAssets>
+    </ProjectReference>
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln
index beab3cc..d9a7b8d 100644
--- a/src/csharp/Grpc.sln
+++ b/src/csharp/Grpc.sln
@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
-VisualStudioVersion = 15.0.26228.4
+VisualStudioVersion = 15.0.26430.4
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Core", "Grpc.Core\Grpc.Core.csproj", "{BD878CB3-BDB4-46AB-84EF-C3B4729F56BC}"
 EndProject
@@ -37,6 +37,8 @@
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Reflection.Tests", "Grpc.Reflection.Tests\Grpc.Reflection.Tests.csproj", "{335AD0A2-F2CC-4C2E-853C-26174206BEE7}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Microbenchmarks", "Grpc.Microbenchmarks\Grpc.Microbenchmarks.csproj", "{84C17746-4727-4290-8E8B-A380793DAE1E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -111,6 +113,10 @@
 		{335AD0A2-F2CC-4C2E-853C-26174206BEE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{335AD0A2-F2CC-4C2E-853C-26174206BEE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{335AD0A2-F2CC-4C2E-853C-26174206BEE7}.Release|Any CPU.Build.0 = Release|Any CPU
+		{84C17746-4727-4290-8E8B-A380793DAE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{84C17746-4727-4290-8E8B-A380793DAE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{84C17746-4727-4290-8E8B-A380793DAE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{84C17746-4727-4290-8E8B-A380793DAE1E}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/src/csharp/build_packages_dotnetcli.bat b/src/csharp/build_packages_dotnetcli.bat
index d823942..cb4ff60 100755
--- a/src/csharp/build_packages_dotnetcli.bat
+++ b/src/csharp/build_packages_dotnetcli.bat
@@ -28,7 +28,7 @@
 @rem OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 @rem Current package versions
-set VERSION=1.4.0-dev
+set VERSION=1.4.0-pre1
 
 @rem Adjust the location of nuget.exe
 set NUGET=C:\nuget\nuget.exe
@@ -51,11 +51,11 @@
 @rem To be able to build, we also need to put grpc_csharp_ext to its normal location
 xcopy /Y /I nativelibs\csharp_ext_windows_x64\grpc_csharp_ext.dll ..\..\cmake\build\x64\Release\
 
-%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Core --output ..\..\..\artifacts || goto :error
-%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Core.Testing --output ..\..\..\artifacts || goto :error
-%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Auth --output ..\..\..\artifacts || goto :error
-%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.HealthCheck --output ..\..\..\artifacts || goto :error
-%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Reflection --output ..\..\..\artifacts || goto :error
+%DOTNET% pack --configuration Release Grpc.Core --output ..\..\..\artifacts || goto :error
+%DOTNET% pack --configuration Release Grpc.Core.Testing --output ..\..\..\artifacts || goto :error
+%DOTNET% pack --configuration Release Grpc.Auth --output ..\..\..\artifacts || goto :error
+%DOTNET% pack --configuration Release Grpc.HealthCheck --output ..\..\..\artifacts || goto :error
+%DOTNET% pack --configuration Release Grpc.Reflection --output ..\..\..\artifacts || goto :error
 
 %NUGET% pack Grpc.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts || goto :error
 %NUGET% pack Grpc.Tools.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts
diff --git a/src/csharp/build_packages_dotnetcli.sh b/src/csharp/build_packages_dotnetcli.sh
index f79c97f..f3dc843 100755
--- a/src/csharp/build_packages_dotnetcli.sh
+++ b/src/csharp/build_packages_dotnetcli.sh
@@ -48,13 +48,13 @@
 mkdir -p ../../libs/opt
 cp nativelibs/csharp_ext_linux_x64/libgrpc_csharp_ext.so ../../libs/opt
 
-dotnet pack --configuration Release --include-symbols --include-source Grpc.Core --output ../../../artifacts
-dotnet pack --configuration Release --include-symbols --include-source Grpc.Core.Testing --output ../../../artifacts
-dotnet pack --configuration Release --include-symbols --include-source Grpc.Auth --output ../../../artifacts
-dotnet pack --configuration Release --include-symbols --include-source Grpc.HealthCheck --output ../../../artifacts
-dotnet pack --configuration Release --include-symbols --include-source Grpc.Reflection --output ../../../artifacts
+dotnet pack --configuration Release Grpc.Core --output ../../../artifacts
+dotnet pack --configuration Release Grpc.Core.Testing --output ../../../artifacts
+dotnet pack --configuration Release Grpc.Auth --output ../../../artifacts
+dotnet pack --configuration Release Grpc.HealthCheck --output ../../../artifacts
+dotnet pack --configuration Release Grpc.Reflection --output ../../../artifacts
 
-nuget pack Grpc.nuspec -Version "1.4.0-dev" -OutputDirectory ../../artifacts
-nuget pack Grpc.Tools.nuspec -Version "1.4.0-dev" -OutputDirectory ../../artifacts
+nuget pack Grpc.nuspec -Version "1.4.0-pre1" -OutputDirectory ../../artifacts
+nuget pack Grpc.Tools.nuspec -Version "1.4.0-pre1" -OutputDirectory ../../artifacts
 
 (cd ../../artifacts && zip csharp_nugets_dotnetcli.zip *.nupkg)
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index f6cff45..a56113e 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -529,6 +529,38 @@
   grpc_call_unref(call);
 }
 
+typedef grpc_call_error (*grpcsharp_call_start_batch_func)(grpc_call *call,
+                                                           const grpc_op *ops,
+                                                           size_t nops,
+                                                           void *tag,
+                                                           void *reserved);
+
+/* Only for testing */
+static grpc_call_error grpcsharp_call_start_batch_nop(grpc_call *call,
+                                                      const grpc_op *ops,
+                                                      size_t nops, void *tag,
+                                                      void *reserved) {
+  return GRPC_CALL_OK;
+}
+
+static grpc_call_error grpcsharp_call_start_batch_default(grpc_call *call,
+                                                          const grpc_op *ops,
+                                                          size_t nops,
+                                                          void *tag,
+                                                          void *reserved) {
+  return grpc_call_start_batch(call, ops, nops, tag, reserved);
+}
+
+static grpcsharp_call_start_batch_func g_call_start_batch_func =
+    grpcsharp_call_start_batch_default;
+
+static grpc_call_error grpcsharp_call_start_batch(grpc_call *call,
+                                                  const grpc_op *ops,
+                                                  size_t nops, void *tag,
+                                                  void *reserved) {
+  return g_call_start_batch_func(call, ops, nops, tag, reserved);
+}
+
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_unary(
     grpc_call *call, grpcsharp_batch_context *ctx, const char *send_buffer,
     size_t send_buffer_len, uint32_t write_flags,
@@ -576,8 +608,8 @@
   ops[5].flags = 0;
   ops[5].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_client_streaming(
@@ -616,8 +648,8 @@
   ops[3].flags = 0;
   ops[3].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming(
@@ -656,8 +688,8 @@
   ops[3].flags = 0;
   ops[3].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_duplex_streaming(
@@ -685,8 +717,8 @@
   ops[1].flags = 0;
   ops[1].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_recv_initial_metadata(
@@ -699,8 +731,8 @@
   ops[0].flags = 0;
   ops[0].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_message(
@@ -720,7 +752,7 @@
   ops[1].flags = 0;
   ops[1].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, nops, ctx, NULL);
+  return grpcsharp_call_start_batch(call, ops, nops, ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_close_from_client(
@@ -731,8 +763,8 @@
   ops[0].flags = 0;
   ops[0].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_status_from_server(
@@ -773,7 +805,7 @@
     ops[nops].reserved = NULL;
     nops++;
   }
-  return grpc_call_start_batch(call, ops, nops, ctx, NULL);
+  return grpcsharp_call_start_batch(call, ops, nops, ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -784,8 +816,8 @@
   ops[0].data.recv_message.recv_message = &(ctx->recv_message);
   ops[0].flags = 0;
   ops[0].reserved = NULL;
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -798,8 +830,8 @@
   ops[0].flags = 0;
   ops[0].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_initial_metadata(
@@ -817,8 +849,8 @@
   ops[0].flags = 0;
   ops[0].reserved = NULL;
 
-  return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
-                               NULL);
+  return grpcsharp_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]),
+                                    ctx, NULL);
 }
 
 GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -1092,3 +1124,17 @@
 GPR_EXPORT int32_t GPR_CALLTYPE grpcsharp_sizeof_grpc_event(void) {
   return sizeof(grpc_event);
 }
+
+/* Override a method for testing */
+GPR_EXPORT void GPR_CALLTYPE
+grpcsharp_test_override_method(const char *method_name, const char *variant) {
+  if (strcmp("grpcsharp_call_start_batch", method_name) == 0) {
+    if (strcmp("nop", variant) == 0) {
+      g_call_start_batch_func = grpcsharp_call_start_batch_nop;
+    } else {
+      GPR_ASSERT(0);
+    }
+  } else {
+    GPR_ASSERT(0);
+  }
+}
diff --git a/src/node/README.md b/src/node/README.md
index 4b90664..3b98b97 100644
--- a/src/node/README.md
+++ b/src/node/README.md
@@ -2,9 +2,9 @@
 # Node.js gRPC Library
 
 ## PREREQUISITES
-- `node`: This requires `node` to be installed, version `0.12` or above. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package.
+- `node`: This requires `node` to be installed, version `4.0` or above. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package.
 
-- **Note:** If you installed `node` via a package manager and the version is still less than `0.12`, try directly installing it from [nodejs.org](https://nodejs.org).
+- **Note:** If you installed `node` via a package manager and the version is still less than `4.0`, try directly installing it from [nodejs.org](https://nodejs.org).
 
 ## INSTALLATION
 
@@ -16,7 +16,7 @@
 
 ## BUILD FROM SOURCE
  1. Clone [the grpc Git Repository](https://github.com/grpc/grpc).
- 2. Run `npm install` from the repository root.
+ 2. Run `npm install --build-from-source` from the repository root.
 
  - **Note:** On Windows, this might fail due to [nodejs issue #4932](https://github.com/nodejs/node/issues/4932) in which case, you will see something like the following in `npm install`'s output (towards the very beginning):
 
@@ -34,61 +34,3 @@
 
 ## TESTING
 To run the test suite, simply run `npm test` in the install location.
-
-## API
-This library internally uses [ProtoBuf.js](https://github.com/dcodeIO/ProtoBuf.js), and some structures it exports match those exported by that library.
-
-If you require this module, you will get an object with the following members
-
-```javascript
-function load(filename)
-```
-
-Takes a filename of a [Protocol Buffer](https://developers.google.com/protocol-buffers/) file, and returns an object representing the structure of the protocol buffer in the following way:
-
- - Namespaces become maps from the names of their direct members to those member objects
- - Service definitions become client constructors for clients for that service. They also have a `service` member that can be used for constructing servers.
- - Message definitions become Message constructors like those that ProtoBuf.js would create
- - Enum definitions become Enum objects like those that ProtoBuf.js would create
- - Anything else becomes the relevant reflection object that ProtoBuf.js would create
-
-
-```javascript
-function loadObject(reflectionObject)
-```
-
-Returns the same structure that `load` returns, but takes a reflection object from `ProtoBuf.js` instead of a file name.
-
-```javascript
-function Server([serverOptions])
-```
-
-Constructs a server to which service/implementation pairs can be added.
-
-
-```javascript
-status
-```
-
-An object mapping status names to status code numbers.
-
-
-```javascript
-callError
-```
-
-An object mapping call error names to codes. This is primarily useful for tracking down certain kinds of internal errors.
-
-
-```javascript
-Credentials
-```
-
-An object with factory methods for creating credential objects for clients.
-
-
-```javascript
-ServerCredentials
-```
-
-An object with factory methods for creating credential objects for servers.
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index 49179ab..9453000 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -508,9 +508,14 @@
 }
 
 Call::Call(grpc_call *call)
-    : wrapped_call(call), pending_batches(0), has_final_op_completed(false) {}
+    : wrapped_call(call), pending_batches(0), has_final_op_completed(false) {
+  peer = grpc_call_get_peer(call);
+}
 
-Call::~Call() { DestroyCall(); }
+Call::~Call() {
+  DestroyCall();
+  gpr_free(peer);
+}
 
 void Call::Init(Local<Object> exports) {
   HandleScope scope;
@@ -662,6 +667,16 @@
   }
   Local<Function> callback_func = info[1].As<Function>();
   Call *call = ObjectWrap::Unwrap<Call>(info.This());
+  if (call->wrapped_call == NULL) {
+    /* This implies that the call has completed and has been destroyed. To
+     * emulate
+     * previous behavior, we should call the callback immediately with an error,
+     * as though the batch had failed in core */
+    Local<Value> argv[] = {
+        Nan::Error("The async function failed because the call has completed")};
+    Nan::Call(callback_func, Nan::New<Object>(), 1, argv);
+    return;
+  }
   Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
   Local<Array> keys = Nan::GetOwnPropertyNames(obj).ToLocalChecked();
   size_t nops = keys->Length();
@@ -727,6 +742,11 @@
     return Nan::ThrowTypeError("cancel can only be called on Call objects");
   }
   Call *call = ObjectWrap::Unwrap<Call>(info.This());
+  if (call->wrapped_call == NULL) {
+    /* Cancel is supposed to be idempotent. If the call has already finished,
+     * cancel should just complete silently */
+    return;
+  }
   grpc_call_error error = grpc_call_cancel(call->wrapped_call, NULL);
   if (error != GRPC_CALL_OK) {
     return Nan::ThrowError(nanErrorWithCode("cancel failed", error));
@@ -747,6 +767,11 @@
         "cancelWithStatus's second argument must be a string");
   }
   Call *call = ObjectWrap::Unwrap<Call>(info.This());
+  if (call->wrapped_call == NULL) {
+    /* Cancel is supposed to be idempotent. If the call has already finished,
+     * cancel should just complete silently */
+    return;
+  }
   grpc_status_code code =
       static_cast<grpc_status_code>(Nan::To<uint32_t>(info[0]).FromJust());
   if (code == GRPC_STATUS_OK) {
@@ -763,9 +788,7 @@
     return Nan::ThrowTypeError("getPeer can only be called on Call objects");
   }
   Call *call = ObjectWrap::Unwrap<Call>(info.This());
-  char *peer = grpc_call_get_peer(call->wrapped_call);
-  Local<Value> peer_value = Nan::New(peer).ToLocalChecked();
-  gpr_free(peer);
+  Local<Value> peer_value = Nan::New(call->peer).ToLocalChecked();
   info.GetReturnValue().Set(peer_value);
 }
 
@@ -780,6 +803,10 @@
         "setCredentials' first argument must be a CallCredentials");
   }
   Call *call = ObjectWrap::Unwrap<Call>(info.This());
+  if (call->wrapped_call == NULL) {
+    return Nan::ThrowError(
+        "Cannot set credentials on a call that has already started");
+  }
   CallCredentials *creds_object = ObjectWrap::Unwrap<CallCredentials>(
       Nan::To<Object>(info[0]).ToLocalChecked());
   grpc_call_credentials *creds = creds_object->GetWrappedCredentials();
diff --git a/src/node/ext/call.h b/src/node/ext/call.h
index 0bd24f5..8f75127 100644
--- a/src/node/ext/call.h
+++ b/src/node/ext/call.h
@@ -96,6 +96,7 @@
      call, this is GRPC_OP_RECV_STATUS_ON_CLIENT and for a server call, this
      is GRPC_OP_SEND_STATUS_FROM_SERVER */
   bool has_final_op_completed;
+  char *peer;
 };
 
 class Op {
diff --git a/src/node/health_check/package.json b/src/node/health_check/package.json
index 37c9b7a..238547c 100644
--- a/src/node/health_check/package.json
+++ b/src/node/health_check/package.json
@@ -1,6 +1,6 @@
 {
   "name": "grpc-health-check",
-  "version": "1.4.0-dev",
+  "version": "1.4.0-pre1",
   "author": "Google Inc.",
   "description": "Health check service for use with gRPC",
   "repository": {
@@ -15,7 +15,7 @@
     }
   ],
   "dependencies": {
-    "grpc": "^1.4.0-dev",
+    "grpc": "^1.4.0-pre1",
     "lodash": "^3.9.3",
     "google-protobuf": "^3.0.0"
   },
diff --git a/src/node/index.js b/src/node/index.js
index 2da77c3..177628e 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,10 +31,6 @@
  *
  */
 
-/**
- * @module
- */
-
 'use strict';
 
 var path = require('path');
@@ -64,24 +60,30 @@
 grpc.setDefaultRootsPem(fs.readFileSync(SSL_ROOTS_PATH, 'ascii'));
 
 /**
- * Load a ProtoBuf.js object as a gRPC object. The options object can provide
- * the following options:
- * - binaryAsBase64: deserialize bytes values as base64 strings instead of
- *   Buffers. Defaults to false
- * - longsAsStrings: deserialize long values as strings instead of objects.
- *   Defaults to true
- * - deprecatedArgumentOrder: Use the beta method argument order for client
- *   methods, with optional arguments after the callback. Defaults to false.
- *   This option is only a temporary stopgap measure to smooth an API breakage.
- *   It is deprecated, and new code should not use it.
- * - protobufjsVersion: Available values are 5, 6, and 'detect'. 5 and 6
- *   respectively indicate that an object from the corresponding version of
- *   ProtoBuf.js is provided in the value argument. If the option is 'detect',
- *   gRPC will guess what the version is based on the structure of the value.
- *   Defaults to 'detect'.
+ * @namespace grpc
+ */
+
+/**
+ * Load a ProtoBuf.js object as a gRPC object.
+ * @memberof grpc
+ * @alias grpc.loadObject
  * @param {Object} value The ProtoBuf.js reflection object to load
  * @param {Object=} options Options to apply to the loaded file
- * @return {Object<string, *>} The resulting gRPC object
+ * @param {bool=} [options.binaryAsBase64=false] deserialize bytes values as
+ *     base64 strings instead of Buffers
+ * @param {bool=} [options.longsAsStrings=true] deserialize long values as
+ *     strings instead of objects
+ * @param {bool=} [options.enumsAsStrings=true] deserialize enum values as
+ *     strings instead of numbers. Only works with Protobuf.js 6 values.
+ * @param {bool=} [options.deprecatedArgumentOrder=false] use the beta method
+ *     argument order for client methods, with optional arguments after the
+ *     callback. This option is only a temporary stopgap measure to smooth an
+ *     API breakage. It is deprecated, and new code should not use it.
+ * @param {(number|string)=} [options.protobufjsVersion='detect'] 5 and 6
+ *     respectively indicate that an object from the corresponding version of
+ *     Protobuf.js is provided in the value argument. If the option is 'detect',
+ *     gRPC wll guess what the version is based on the structure of the value.
+ * @return {Object<string, *>} The resulting gRPC object.
  */
 exports.loadObject = function loadObject(value, options) {
   options = _.defaults(options, common.defaultGrpcOptions);
@@ -112,22 +114,23 @@
 var loadObject = exports.loadObject;
 
 /**
- * Load a gRPC object from a .proto file. The options object can provide the
- * following options:
- * - convertFieldsToCamelCase: Load this file with field names in camel case
- *   instead of their original case
- * - binaryAsBase64: deserialize bytes values as base64 strings instead of
- *   Buffers. Defaults to false
- * - longsAsStrings: deserialize long values as strings instead of objects.
- *   Defaults to true
- * - deprecatedArgumentOrder: Use the beta method argument order for client
- *   methods, with optional arguments after the callback. Defaults to false.
- *   This option is only a temporary stopgap measure to smooth an API breakage.
- *   It is deprecated, and new code should not use it.
+ * Load a gRPC object from a .proto file.
+ * @memberof grpc
+ * @alias grpc.load
  * @param {string|{root: string, file: string}} filename The file to load
  * @param {string=} format The file format to expect. Must be either 'proto' or
  *     'json'. Defaults to 'proto'
  * @param {Object=} options Options to apply to the loaded file
+ * @param {bool=} [options.convertFieldsToCamelCase=false] Load this file with
+ *     field names in camel case instead of their original case
+ * @param {bool=} [options.binaryAsBase64=false] deserialize bytes values as
+ *     base64 strings instead of Buffers
+ * @param {bool=} [options.longsAsStrings=true] deserialize long values as
+ *     strings instead of objects
+ * @param {bool=} [options.deprecatedArgumentOrder=false] use the beta method
+ *     argument order for client methods, with optional arguments after the
+ *     callback. This option is only a temporary stopgap measure to smooth an
+ *     API breakage. It is deprecated, and new code should not use it.
  * @return {Object<string, *>} The resulting gRPC object
  */
 exports.load = function load(filename, format, options) {
@@ -168,6 +171,8 @@
  * called. Note: the output format here is intended to be informational, and
  * is not guaranteed to stay the same in the future.
  * Logs will be directed to logger.error.
+ * @memberof grpc
+ * @alias grpc.setLogger
  * @param {Console} logger A Console-like object.
  */
 exports.setLogger = function setLogger(logger) {
@@ -187,6 +192,8 @@
 /**
  * Sets the logger verbosity for gRPC module logging. The options are members
  * of the grpc.logVerbosity map.
+ * @memberof grpc
+ * @alias grpc.setLogVerbosity
  * @param {Number} verbosity The minimum severity to log
  */
 exports.setLogVerbosity = function setLogVerbosity(verbosity) {
@@ -194,71 +201,70 @@
   grpc.setLogVerbosity(verbosity);
 };
 
-/**
- * @see module:src/server.Server
- */
 exports.Server = server.Server;
 
-/**
- * @see module:src/metadata
- */
 exports.Metadata = Metadata;
 
-/**
- * Status name to code number mapping
- */
 exports.status = constants.status;
 
-/**
- * Propagate flag name to number mapping
- */
 exports.propagate = constants.propagate;
 
-/**
- * Call error name to code number mapping
- */
 exports.callError = constants.callError;
 
-/**
- * Write flag name to code number mapping
- */
 exports.writeFlags = constants.writeFlags;
 
-/**
- * Log verbosity setting name to code number mapping
- */
 exports.logVerbosity = constants.logVerbosity;
 
-/**
- * Credentials factories
- */
 exports.credentials = require('./src/credentials.js');
 
 /**
  * ServerCredentials factories
+ * @constructor ServerCredentials
+ * @memberof grpc
  */
 exports.ServerCredentials = grpc.ServerCredentials;
 
 /**
- * @see module:src/client.makeClientConstructor
+ * Create insecure server credentials
+ * @name grpc.ServerCredentials.createInsecure
+ * @kind function
+ * @return grpc.ServerCredentials
  */
+
+/**
+ * A private key and certificate pair
+ * @typedef {Object} grpc.ServerCredentials~keyCertPair
+ * @property {Buffer} privateKey The server's private key
+ * @property {Buffer} certChain The server's certificate chain
+ */
+
+/**
+ * Create SSL server credentials
+ * @name grpc.ServerCredentials.createInsecure
+ * @kind function
+ * @param {?Buffer} rootCerts Root CA certificates for validating client
+ *     certificates
+ * @param {Array<grpc.ServerCredentials~keyCertPair>} keyCertPairs A list of
+ *     private key and certificate chain pairs to be used for authenticating
+ *     the server
+ * @param {boolean} [checkClientCertificate=false] Indicates that the server
+ *     should request and verify the client's certificates
+ * @return grpc.ServerCredentials
+ */
+
 exports.makeGenericClientConstructor = client.makeClientConstructor;
 
-/**
- * @see module:src/client.getClientChannel
- */
 exports.getClientChannel = client.getClientChannel;
 
-/**
- * @see module:src/client.waitForClientReady
- */
 exports.waitForClientReady = client.waitForClientReady;
 
+/**
+ * @memberof grpc
+ * @alias grpc.closeClient
+ * @param {grpc.Client} client_obj The client to close
+ */
 exports.closeClient = function closeClient(client_obj) {
   client.Client.prototype.close.apply(client_obj);
 };
 
-/**
- * @see module:src/client.Client
- */
 exports.Client = client.Client;
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 16fe06a..f59ac5c 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -43,8 +43,6 @@
  * var Client = proto_obj.package.subpackage.ServiceName;
  * var client = new Client(server_address, client_credentials);
  * var call = client.unaryMethod(arguments, callback);
- *
- * @module
  */
 
 'use strict';
@@ -70,13 +68,26 @@
 var util = require('util');
 var version = require('../../../package.json').version;
 
+/**
+ * Initial response metadata sent by the server when it starts processing the
+ * call
+ * @event grpc~ClientUnaryCall#metadata
+ * @type {grpc.Metadata}
+ */
+
+/**
+ * Status of the call when it has completed.
+ * @event grpc~ClientUnaryCall#status
+ * @type grpc~StatusObject
+ */
+
 util.inherits(ClientUnaryCall, EventEmitter);
 
 /**
- * An EventEmitter. Used for unary calls
- * @constructor
+ * An EventEmitter. Used for unary calls.
+ * @constructor grpc~ClientUnaryCall
  * @extends external:EventEmitter
- * @param {grpc.Call} call The call object associated with the request
+ * @param {grpc.internal~Call} call The call object associated with the request
  */
 function ClientUnaryCall(call) {
   EventEmitter.call(this);
@@ -88,14 +99,16 @@
 /**
  * A stream that the client can write to. Used for calls that are streaming from
  * the client side.
- * @constructor
+ * @constructor grpc~ClientWritableStream
  * @extends external:Writable
- * @borrows module:src/client~ClientUnaryCall#cancel as
- *     module:src/client~ClientWritableStream#cancel
- * @borrows module:src/client~ClientUnaryCall#getPeer as
- *     module:src/client~ClientWritableStream#getPeer
- * @param {grpc.Call} call The call object to send data with
- * @param {module:src/common~serialize=} [serialize=identity] Serialization
+ * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientWritableStream#cancel
+ * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientWritableStream#getPeer
+ * @borrows grpc~ClientUnaryCall#event:metadata as
+ *     grpc~ClientWritableStream#metadata
+ * @borrows grpc~ClientUnaryCall#event:status as
+ *     grpc~ClientWritableStream#status
+ * @param {grpc.internal~Call} call The call object to send data with
+ * @param {grpc~serialize=} [serialize=identity] Serialization
  *     function for writes.
  */
 function ClientWritableStream(call, serialize) {
@@ -110,17 +123,36 @@
 }
 
 /**
+ * Write a message to the request stream. If serializing the argument fails,
+ * the call will be cancelled and the stream will end with an error.
+ * @name grpc~ClientWritableStream#write
+ * @kind function
+ * @override
+ * @param {*} message The message to write. Must be a valid argument to the
+ *     serialize function of the corresponding method
+ * @param {grpc.writeFlags} flags Flags to modify how the message is written
+ * @param {Function} callback Callback for when this chunk of data is flushed
+ * @return {boolean} As defined for [Writable]{@link external:Writable}
+ */
+
+/**
  * Attempt to write the given chunk. Calls the callback when done. This is an
  * implementation of a method needed for implementing stream.Writable.
- * @access private
- * @param {Buffer} chunk The chunk to write
- * @param {string} encoding Used to pass write flags
+ * @private
+ * @param {*} chunk The chunk to write
+ * @param {grpc.writeFlags} encoding Used to pass write flags
  * @param {function(Error=)} callback Called when the write is complete
  */
 function _write(chunk, encoding, callback) {
   /* jshint validthis: true */
   var batch = {};
   var message;
+  var self = this;
+  if (this.writeFailed) {
+    /* Once a write fails, just call the callback immediately to let the caller
+       flush any pending writes. */
+    setImmediate(callback);
+  }
   try {
     message = this.serialize(chunk);
   } catch (e) {
@@ -141,8 +173,10 @@
   batch[grpc.opType.SEND_MESSAGE] = message;
   this.call.startBatch(batch, function(err, event) {
     if (err) {
-      // Something has gone wrong. Stop writing by failing to call callback
-      return;
+      /* Assume that the call is complete and that writing failed because a
+         status was received. In that case, set a flag to discard all future
+         writes */
+      self.writeFailed = true;
     }
     callback();
   });
@@ -155,14 +189,16 @@
 /**
  * A stream that the client can read from. Used for calls that are streaming
  * from the server side.
- * @constructor
+ * @constructor grpc~ClientReadableStream
  * @extends external:Readable
- * @borrows module:src/client~ClientUnaryCall#cancel as
- *     module:src/client~ClientReadableStream#cancel
- * @borrows module:src/client~ClientUnaryCall#getPeer as
- *     module:src/client~ClientReadableStream#getPeer
- * @param {grpc.Call} call The call object to read data with
- * @param {module:src/common~deserialize=} [deserialize=identity]
+ * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientReadableStream#cancel
+ * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientReadableStream#getPeer
+ * @borrows grpc~ClientUnaryCall#event:metadata as
+ *     grpc~ClientReadableStream#metadata
+ * @borrows grpc~ClientUnaryCall#event:status as
+ *     grpc~ClientReadableStream#status
+ * @param {grpc.internal~Call} call The call object to read data with
+ * @param {grpc~deserialize=} [deserialize=identity]
  *     Deserialization function for reads
  */
 function ClientReadableStream(call, deserialize) {
@@ -183,7 +219,7 @@
  * parameter indicates that the call should end with that status. status
  * defaults to OK if not provided.
  * @param {Object!} status The status that the call should end with
- * @access private
+ * @private
  */
 function _readsDone(status) {
   /* jshint validthis: true */
@@ -202,7 +238,7 @@
 
 /**
  * Called to indicate that we have received a status from the server.
- * @access private
+ * @private
  */
 function _receiveStatus(status) {
   /* jshint validthis: true */
@@ -215,7 +251,7 @@
 /**
  * If we have both processed all incoming messages and received the status from
  * the server, emit the status. Otherwise, do nothing.
- * @access private
+ * @private
  */
 function _emitStatusIfDone() {
   /* jshint validthis: true */
@@ -242,7 +278,7 @@
 
 /**
  * Read the next object from the stream.
- * @access private
+ * @private
  * @param {*} size Ignored because we use objectMode=true
  */
 function _read(size) {
@@ -300,16 +336,19 @@
 /**
  * A stream that the client can read from or write to. Used for calls with
  * duplex streaming.
- * @constructor
+ * @constructor grpc~ClientDuplexStream
  * @extends external:Duplex
- * @borrows module:src/client~ClientUnaryCall#cancel as
- *     module:src/client~ClientDuplexStream#cancel
- * @borrows module:src/client~ClientUnaryCall#getPeer as
- *     module:src/client~ClientDuplexStream#getPeer
- * @param {grpc.Call} call Call object to proxy
- * @param {module:src/common~serialize=} [serialize=identity] Serialization
+ * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientDuplexStream#cancel
+ * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientDuplexStream#getPeer
+ * @borrows grpc~ClientWritableStream#write as grpc~ClientDuplexStream#write
+ * @borrows grpc~ClientUnaryCall#event:metadata as
+ *     grpc~ClientDuplexStream#metadata
+ * @borrows grpc~ClientUnaryCall#event:status as
+ *     grpc~ClientDuplexStream#status
+ * @param {grpc.internal~Call} call Call object to proxy
+ * @param {grpc~serialize=} [serialize=identity] Serialization
  *     function for requests
- * @param {module:src/common~deserialize=} [deserialize=identity]
+ * @param {grpc~deserialize=} [deserialize=identity]
  *     Deserialization function for responses
  */
 function ClientDuplexStream(call, serialize, deserialize) {
@@ -336,8 +375,9 @@
 ClientDuplexStream.prototype._write = _write;
 
 /**
- * Cancel the ongoing call
- * @alias module:src/client~ClientUnaryCall#cancel
+ * Cancel the ongoing call. Results in the call ending with a CANCELLED status,
+ * unless it has already ended with some other status.
+ * @alias grpc~ClientUnaryCall#cancel
  */
 function cancel() {
   /* jshint validthis: true */
@@ -352,7 +392,7 @@
 /**
  * Get the endpoint this call/stream is connected to.
  * @return {string} The URI of the endpoint
- * @alias module:src/client~ClientUnaryCall#getPeer
+ * @alias grpc~ClientUnaryCall#getPeer
  */
 function getPeer() {
   /* jshint validthis: true */
@@ -368,33 +408,31 @@
  * Any client call type
  * @typedef {(ClientUnaryCall|ClientReadableStream|
  *            ClientWritableStream|ClientDuplexStream)}
- *     module:src/client~Call
+ *     grpc.Client~Call
  */
 
 /**
  * Options that can be set on a call.
- * @typedef {Object} module:src/client~CallOptions
- * @property {(date|number)} deadline The deadline for the entire call to
- *     complete. A value of Infinity indicates that no deadline should be set.
- * @property {(string)} host Server hostname to set on the call. Only meaningful
+ * @typedef {Object} grpc.Client~CallOptions
+ * @property {grpc~Deadline} deadline The deadline for the entire call to
+ *     complete.
+ * @property {string} host Server hostname to set on the call. Only meaningful
  *     if different from the server address used to construct the client.
- * @property {module:src/client~Call} parent Parent call. Used in servers when
+ * @property {grpc.Client~Call} parent Parent call. Used in servers when
  *     making a call as part of the process of handling a call. Used to
  *     propagate some information automatically, as specified by
  *     propagate_flags.
  * @property {number} propagate_flags Indicates which properties of a parent
  *     call should propagate to this call. Bitwise combination of flags in
- *     [grpc.propagate]{@link module:index.propagate}.
- * @property {module:src/credentials~CallCredentials} credentials The
- *     credentials that should be used to make this particular call.
+ *     {@link grpc.propagate}.
+ * @property {grpc.credentials~CallCredentials} credentials The credentials that
+ *     should be used to make this particular call.
  */
 
 /**
- * Get a call object built with the provided options. Keys for options are
- * 'deadline', which takes a date or number, and 'host', which takes a string
- * and overrides the hostname to connect to.
+ * Get a call object built with the provided options.
  * @access private
- * @param {module:src/client~CallOptions=} options Options object.
+ * @param {grpc.Client~CallOptions=} options Options object.
  */
 function getCall(channel, method, options) {
   var deadline;
@@ -422,14 +460,14 @@
 
 /**
  * A generic gRPC client. Primarily useful as a base class for generated clients
- * @alias module:src/client.Client
+ * @memberof grpc
  * @constructor
  * @param {string} address Server address to connect to
- * @param {module:src/credentials~ChannelCredentials} credentials Credentials to
- *     use to connect to the server
+ * @param {grpc~ChannelCredentials} credentials Credentials to use to connect to
+ *     the server
  * @param {Object} options Options to apply to channel creation
  */
-var Client = exports.Client = function Client(address, credentials, options) {
+function Client(address, credentials, options) {
   if (!options) {
     options = {};
   }
@@ -445,19 +483,13 @@
   /* Private fields use $ as a prefix instead of _ because it is an invalid
    * prefix of a method name */
   this.$channel = new grpc.Channel(address, credentials, options);
-};
+}
+
+exports.Client = Client;
 
 /**
- * @typedef {Error} module:src/client.Client~ServiceError
- * @property {number} code The error code, a key of
- *     [grpc.status]{@link module:src/client.status}
- * @property {module:metadata.Metadata} metadata Metadata sent with the status
- *     by the server, if any
- */
-
-/**
- * @callback module:src/client.Client~requestCallback
- * @param {?module:src/client.Client~ServiceError} error The error, if the call
+ * @callback grpc.Client~requestCallback
+ * @param {?grpc~ServiceError} error The error, if the call
  *     failed
  * @param {*} value The response value, if the call succeeded
  */
@@ -466,17 +498,17 @@
  * Make a unary request to the given method, using the given serialize
  * and deserialize functions, with the given argument.
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
+ * @param {grpc~serialize} serialize The serialization function for
  *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
  * @param {*} argument The argument to the call. Should be serializable with
  *     serialize
- * @param {module:src/metadata.Metadata=} metadata Metadata to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @param {module:src/client.Client~requestCallback} callback The callback to
+ * @param {grpc.Metadata=} metadata Metadata to add to the call
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @param {grpc.Client~requestCallback} callback The callback to
  *     for when the response is received
- * @return {EventEmitter} An event emitter for stream related events
+ * @return {grpc~ClientUnaryCall} An event emitter for stream related events
  */
 Client.prototype.makeUnaryRequest = function(method, serialize, deserialize,
                                              argument, metadata, options,
@@ -548,17 +580,17 @@
  * Make a client stream request to the given method, using the given serialize
  * and deserialize functions, with the given argument.
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
+ * @param {grpc~serialize} serialize The serialization function for
  *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
- * @param {module:src/metadata.Metadata=} metadata Array of metadata key/value
- *     pairs to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @param {Client~requestCallback} callback The callback to for when the
+ * @param {grpc.Metadata=} metadata Array of metadata key/value pairs to add to
+ *     the call
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @param {grpc.Client~requestCallback} callback The callback to for when the
  *     response is received
- * @return {module:src/client~ClientWritableStream} An event emitter for stream
- *     related events
+ * @return {grpc~ClientWritableStream} An event emitter for stream related
+ *     events
  */
 Client.prototype.makeClientStreamRequest = function(method, serialize,
                                                       deserialize, metadata,
@@ -631,17 +663,16 @@
  * Make a server stream request to the given method, with the given serialize
  * and deserialize function, using the given argument
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
- *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~serialize} serialize The serialization function for inputs
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
  * @param {*} argument The argument to the call. Should be serializable with
  *     serialize
- * @param {module:src/metadata.Metadata=} metadata Array of metadata key/value
- *     pairs to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @return {module:src/client~ClientReadableStream} An event emitter for stream
- *     related events
+ * @param {grpc.Metadata=} metadata Array of metadata key/value pairs to add to
+ *     the call
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @return {grpc~ClientReadableStream} An event emitter for stream related
+ *     events
  */
 Client.prototype.makeServerStreamRequest = function(method, serialize,
                                                     deserialize, argument,
@@ -693,15 +724,13 @@
 /**
  * Make a bidirectional stream request with this method on the given channel.
  * @param {string} method The name of the method to request
- * @param {module:src/common~serialize} serialize The serialization function for
- *     inputs
- * @param {module:src/common~deserialize} deserialize The deserialization
+ * @param {grpc~serialize} serialize The serialization function for inputs
+ * @param {grpc~deserialize} deserialize The deserialization
  *     function for outputs
- * @param {module:src/metadata.Metadata=} metadata Array of metadata key/value
+ * @param {grpc.Metadata=} metadata Array of metadata key/value
  *     pairs to add to the call
- * @param {module:src/client~CallOptions=} options Options map
- * @return {module:src/client~ClientDuplexStream} An event emitter for stream
- *     related events
+ * @param {grpc.Client~CallOptions=} options Options map
+ * @return {grpc~ClientDuplexStream} An event emitter for stream related events
  */
 Client.prototype.makeBidiStreamRequest = function(method, serialize,
                                                   deserialize, metadata,
@@ -743,6 +772,9 @@
   return stream;
 };
 
+/**
+ * Close this client.
+ */
 Client.prototype.close = function() {
   this.$channel.close();
 };
@@ -761,8 +793,7 @@
  * with an error if the attempt to connect to the server has unrecoverablly
  * failed or if the deadline expires. This function will make the channel
  * start connecting if it has not already done so.
- * @param {(Date|Number)} deadline When to stop waiting for a connection. Pass
- *     Infinity to wait forever.
+ * @param {grpc~Deadline} deadline When to stop waiting for a connection.
  * @param {function(Error)} callback The callback to call when done attempting
  *     to connect.
  */
@@ -788,7 +819,7 @@
 /**
  * Map with short names for each of the requester maker functions. Used in
  * makeClientConstructor
- * @access private
+ * @private
  */
 var requester_funcs = {
   unary: Client.prototype.makeUnaryRequest,
@@ -834,9 +865,15 @@
 
 /**
  * Creates a constructor for a client with the given methods, as specified in
- * the methods argument.
- * @param {module:src/common~ServiceDefinition} methods An object mapping
- *     method names to method attributes
+ * the methods argument. The resulting class will have an instance method for
+ * each method in the service, which is a partial application of one of the
+ * [Client]{@link grpc.Client} request methods, depending on `requestSerialize`
+ * and `responseSerialize`, with the `method`, `serialize`, and `deserialize`
+ * arguments predefined.
+ * @memberof grpc
+ * @alias grpc~makeGenericClientConstructor
+ * @param {grpc~ServiceDefinition} methods An object mapping method names to
+ *     method attributes
  * @param {string} serviceName The fully qualified name of the service
  * @param {Object} class_options An options object.
  * @param {boolean=} [class_options.deprecatedArgumentOrder=false] Indicates
@@ -844,9 +881,8 @@
  *     arguments at the end instead of the callback at the end. This option
  *     is only a temporary stopgap measure to smooth an API breakage.
  *     It is deprecated, and new code should not use it.
- * @return {function(string, Object)} New client constructor, which is a
- *     subclass of [grpc.Client]{@link module:src/client.Client}, and has the
- *     same arguments as that constructor.
+ * @return {function} New client constructor, which is a subclass of
+ *     {@link grpc.Client}, and has the same arguments as that constructor.
  */
 exports.makeClientConstructor = function(methods, serviceName,
                                          class_options) {
@@ -898,8 +934,11 @@
 
 /**
  * Return the underlying channel object for the specified client
+ * @memberof grpc
+ * @alias grpc~getClientChannel
  * @param {Client} client
  * @return {Channel} The channel
+ * @see grpc.Client#getChannel
  */
 exports.getClientChannel = function(client) {
   return Client.prototype.getChannel.call(client);
@@ -911,22 +950,15 @@
  * with an error if the attempt to connect to the server has unrecoverablly
  * failed or if the deadline expires. This function will make the channel
  * start connecting if it has not already done so.
+ * @memberof grpc
+ * @alias grpc~waitForClientReady
  * @param {Client} client The client to wait on
- * @param {(Date|Number)} deadline When to stop waiting for a connection. Pass
+ * @param {grpc~Deadline} deadline When to stop waiting for a connection. Pass
  *     Infinity to wait forever.
  * @param {function(Error)} callback The callback to call when done attempting
  *     to connect.
+ * @see grpc.Client#waitForReady
  */
 exports.waitForClientReady = function(client, deadline, callback) {
   Client.prototype.waitForReady.call(client, deadline, callback);
 };
-
-/**
- * Map of status code names to status codes
- */
-exports.status = constants.status;
-
-/**
- * See docs for client.callError
- */
-exports.callError = grpc.callError;
diff --git a/src/node/src/common.js b/src/node/src/common.js
index 4dad60e..0f83531 100644
--- a/src/node/src/common.js
+++ b/src/node/src/common.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,12 +31,6 @@
  *
  */
 
-/**
- * This module contains functions that are common to client and server
- * code. None of them should be used directly by gRPC users.
- * @module
- */
-
 'use strict';
 
 var _ = require('lodash');
@@ -62,16 +56,19 @@
 
 /**
  * The logger object for the gRPC module. Defaults to console.
+ * @private
  */
 exports.logger = console;
 
 /**
  * The current logging verbosity. 0 corresponds to logging everything
+ * @private
  */
 exports.logVerbosity = 0;
 
 /**
  * Log a message if the severity is at least as high as the current verbosity
+ * @private
  * @param {Number} severity A value of the grpc.logVerbosity map
  * @param {String} message The message to log
  */
@@ -83,6 +80,7 @@
 
 /**
  * Default options for loading proto files into gRPC
+ * @alias grpc~defaultLoadOptions
  */
 exports.defaultGrpcOptions = {
   convertFieldsToCamelCase: false,
@@ -95,6 +93,30 @@
 // JSDoc definitions that are used in multiple other modules
 
 /**
+ * Represents the status of a completed request. If `code` is
+ * {@link grpc.status}.OK, then the request has completed successfully.
+ * Otherwise, the request has failed, `details` will contain a description of
+ * the error. Either way, `metadata` contains the trailing response metadata
+ * sent by the server when it finishes processing the call.
+ * @typedef {object} grpc~StatusObject
+ * @property {number} code The error code, a key of {@link grpc.status}
+ * @property {string} details Human-readable description of the status
+ * @property {grpc.Metadata} metadata Trailing metadata sent with the status,
+ *     if applicable
+ */
+
+/**
+ * Describes how a request has failed. The member `message` will be the same as
+ * `details` in {@link grpc~StatusObject}, and `code` and `metadata` are the
+ * same as in that object.
+ * @typedef {Error} grpc~ServiceError
+ * @property {number} code The error code, a key of {@link grpc.status} that is
+ *     not `grpc.status.OK`
+ * @property {grpc.Metadata} metadata Trailing metadata sent with the status,
+ *     if applicable
+ */
+
+/**
  * The EventEmitter class in the event standard module
  * @external EventEmitter
  * @see https://nodejs.org/api/events.html#events_class_eventemitter
@@ -120,38 +142,46 @@
 
 /**
  * A serialization function
- * @callback module:src/common~serialize
+ * @callback grpc~serialize
  * @param {*} value The value to serialize
  * @return {Buffer} The value serialized as a byte sequence
  */
 
 /**
  * A deserialization function
- * @callback module:src/common~deserialize
+ * @callback grpc~deserialize
  * @param {Buffer} data The byte sequence to deserialize
  * @return {*} The data deserialized as a value
  */
 
 /**
+ * The deadline of an operation. If it is a date, the deadline is reached at
+ * the date and time specified. If it is a finite number, it is treated as
+ * a number of milliseconds since the Unix Epoch. If it is Infinity, the
+ * deadline will never be reached. If it is -Infinity, the deadline has already
+ * passed.
+ * @typedef {(number|date)} grpc~Deadline
+ */
+
+/**
  * An object that completely defines a service method signature.
- * @typedef {Object} module:src/common~MethodDefinition
+ * @typedef {Object} grpc~MethodDefinition
  * @property {string} path The method's URL path
  * @property {boolean} requestStream Indicates whether the method accepts
  *     a stream of requests
  * @property {boolean} responseStream Indicates whether the method returns
  *     a stream of responses
- * @property {module:src/common~serialize} requestSerialize Serialization
+ * @property {grpc~serialize} requestSerialize Serialization
  *     function for request values
- * @property {module:src/common~serialize} responseSerialize Serialization
+ * @property {grpc~serialize} responseSerialize Serialization
  *     function for response values
- * @property {module:src/common~deserialize} requestDeserialize Deserialization
+ * @property {grpc~deserialize} requestDeserialize Deserialization
  *     function for request data
- * @property {module:src/common~deserialize} responseDeserialize Deserialization
+ * @property {grpc~deserialize} responseDeserialize Deserialization
  *     function for repsonse data
  */
 
 /**
  * An object that completely defines a service.
- * @typedef {Object.<string, module:src/common~MethodDefinition>}
- *     module:src/common~ServiceDefinition
+ * @typedef {Object.<string, grpc~MethodDefinition>} grpc~ServiceDefinition
  */
diff --git a/src/node/src/constants.js b/src/node/src/constants.js
index 528dab1..c441ee7 100644
--- a/src/node/src/constants.js
+++ b/src/node/src/constants.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2017, Google Inc.
  * All rights reserved.
  *
@@ -31,16 +31,14 @@
  *
  */
 
-/**
- * @module
- */
-
 /* The comments about status codes are copied verbatim (with some formatting
  * modifications) from include/grpc/impl/codegen/status.h, for the purpose of
  * including them in generated documentation.
  */
 /**
  * Enum of status codes that gRPC can return
+ * @memberof grpc
+ * @alias grpc.status
  * @readonly
  * @enum {number}
  */
@@ -178,6 +176,8 @@
  * Users are encouraged to write propagation masks as deltas from the default.
  * i.e. write `grpc.propagate.DEFAULTS & ~grpc.propagate.DEADLINE` to disable
  * deadline propagation.
+ * @memberof grpc
+ * @alias grpc.propagate
  * @enum {number}
  */
 exports.propagate = {
@@ -194,9 +194,11 @@
 /**
  * Call error constants. Call errors almost always indicate bugs in the gRPC
  * library, and these error codes are mainly useful for finding those bugs.
+ * @memberof grpc
+ * @readonly
  * @enum {number}
  */
-exports.callError = {
+const callError = {
   OK: 0,
   ERROR: 1,
   NOT_ON_SERVER: 2,
@@ -213,9 +215,14 @@
   PAYLOAD_TYPE_MISMATCH: 14
 };
 
+exports.callError = callError;
+
 /**
  * Write flags: these can be bitwise or-ed to form write options that modify
  * how data is written.
+ * @memberof grpc
+ * @alias grpc.writeFlags
+ * @readonly
  * @enum {number}
  */
 exports.writeFlags = {
@@ -232,6 +239,9 @@
 };
 
 /**
+ * @memberof grpc
+ * @alias grpc.logVerbosity
+ * @readonly
  * @enum {number}
  */
 exports.logVerbosity = {
diff --git a/src/node/src/credentials.js b/src/node/src/credentials.js
index b1e86bb..b1dbc1c 100644
--- a/src/node/src/credentials.js
+++ b/src/node/src/credentials.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -48,6 +48,7 @@
  * For example, to create a client secured with SSL that uses Google
  * default application credentials to authenticate:
  *
+ * @example
  * var channel_creds = credentials.createSsl(root_certs);
  * (new GoogleAuth()).getApplicationDefault(function(err, credential) {
  *   var call_creds = credentials.createFromGoogleCredential(credential);
@@ -56,15 +57,25 @@
  *   var client = new Client(address, combined_creds);
  * });
  *
- * @module
+ * @namespace grpc.credentials
  */
 
 'use strict';
 
 var grpc = require('./grpc_extension');
 
+/**
+ * This cannot be constructed directly. Instead, instances of this class should
+ * be created using the factory functions in {@link grpc.credentials}
+ * @constructor grpc.credentials~CallCredentials
+ */
 var CallCredentials = grpc.CallCredentials;
 
+/**
+ * This cannot be constructed directly. Instead, instances of this class should
+ * be created using the factory functions in {@link grpc.credentials}
+ * @constructor grpc.credentials~ChannelCredentials
+ */
 var ChannelCredentials = grpc.ChannelCredentials;
 
 var Metadata = require('./metadata.js');
@@ -76,24 +87,48 @@
 var _ = require('lodash');
 
 /**
+ * @external GoogleCredential
+ * @see https://github.com/google/google-auth-library-nodejs
+ */
+
+/**
  * Create an SSL Credentials object. If using a client-side certificate, both
  * the second and third arguments must be passed.
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createSsl
+ * @kind function
  * @param {Buffer} root_certs The root certificate data
  * @param {Buffer=} private_key The client certificate private key, if
  *     applicable
  * @param {Buffer=} cert_chain The client certificate cert chain, if applicable
- * @return {ChannelCredentials} The SSL Credentials object
+ * @return {grpc.credentials.ChannelCredentials} The SSL Credentials object
  */
 exports.createSsl = ChannelCredentials.createSsl;
 
 /**
+ * @callback grpc.credentials~metadataCallback
+ * @param {Error} error The error, if getting metadata failed
+ * @param {grpc.Metadata} metadata The metadata
+ */
+
+/**
+ * @callback grpc.credentials~generateMetadata
+ * @param {Object} params Parameters that can modify metadata generation
+ * @param {string} params.service_url The URL of the service that the call is
+ *     going to
+ * @param {grpc.credentials~metadataCallback} callback
+ */
+
+/**
  * Create a gRPC credentials object from a metadata generation function. This
  * function gets the service URL and a callback as parameters. The error
  * passed to the callback can optionally have a 'code' value attached to it,
  * which corresponds to a status code that this library uses.
- * @param {function(String, function(Error, Metadata))} metadata_generator The
- *     function that generates metadata
- * @return {CallCredentials} The credentials object
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createFromMetadataGenerator
+ * @param {grpc.credentials~generateMetadata} metadata_generator The function
+ *     that generates metadata
+ * @return {grpc.credentials.CallCredentials} The credentials object
  */
 exports.createFromMetadataGenerator = function(metadata_generator) {
   return CallCredentials.createFromPlugin(function(service_url, cb_data,
@@ -119,8 +154,11 @@
 
 /**
  * Create a gRPC credential from a Google credential object.
- * @param {Object} google_credential The Google credential object to use
- * @return {CallCredentials} The resulting credentials object
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createFromGoogleCredential
+ * @param {external:GoogleCredential} google_credential The Google credential
+ *     object to use
+ * @return {grpc.credentials.CallCredentials} The resulting credentials object
  */
 exports.createFromGoogleCredential = function(google_credential) {
   return exports.createFromMetadataGenerator(function(auth_context, callback) {
@@ -141,6 +179,8 @@
 /**
  * Combine a ChannelCredentials with any number of CallCredentials into a single
  * ChannelCredentials object.
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.combineChannelCredentials
  * @param {ChannelCredentials} channel_credential The ChannelCredentials to
  *     start with
  * @param {...CallCredentials} credentials The CallCredentials to compose
@@ -157,6 +197,8 @@
 
 /**
  * Combine any number of CallCredentials into a single CallCredentials object
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.combineCallCredentials
  * @param {...CallCredentials} credentials the CallCredentials to compose
  * @return CallCredentials A credentials object that combines all of the input
  *     credentials
@@ -172,6 +214,9 @@
 /**
  * Create an insecure credentials object. This is used to create a channel that
  * does not use SSL. This cannot be composed with anything.
+ * @memberof grpc.credentials
+ * @alias grpc.credentials.createInsecure
+ * @kind function
  * @return {ChannelCredentials} The insecure credentials object
  */
 exports.createInsecure = ChannelCredentials.createInsecure;
diff --git a/src/node/src/grpc_extension.js b/src/node/src/grpc_extension.js
index 63a281d..864da97 100644
--- a/src/node/src/grpc_extension.js
+++ b/src/node/src/grpc_extension.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2016, Google Inc.
  * All rights reserved.
  *
diff --git a/src/node/src/metadata.js b/src/node/src/metadata.js
index 92cf239..c757d52 100644
--- a/src/node/src/metadata.js
+++ b/src/node/src/metadata.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,15 +31,6 @@
  *
  */
 
-/**
- * Metadata module
- *
- * This module defines the Metadata class, which represents header and trailer
- * metadata for gRPC calls.
- *
- * @module
- */
-
 'use strict';
 
 var _ = require('lodash');
@@ -48,8 +39,8 @@
 
 /**
  * Class for storing metadata. Keys are normalized to lowercase ASCII.
+ * @memberof grpc
  * @constructor
- * @alias module:src/metadata.Metadata
  * @example
  * var metadata = new metadata_module.Metadata();
  * metadata.set('key1', 'value1');
diff --git a/src/node/src/protobuf_js_5_common.js b/src/node/src/protobuf_js_5_common.js
index 4041e05..1663a3a 100644
--- a/src/node/src/protobuf_js_5_common.js
+++ b/src/node/src/protobuf_js_5_common.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2017, Google Inc.
  * All rights reserved.
  *
@@ -31,6 +31,11 @@
  *
  */
 
+/**
+ * @module
+ * @private
+ */
+
 'use strict';
 
 var _ = require('lodash');
diff --git a/src/node/src/protobuf_js_6_common.js b/src/node/src/protobuf_js_6_common.js
index 00f11f2..91a458a 100644
--- a/src/node/src/protobuf_js_6_common.js
+++ b/src/node/src/protobuf_js_6_common.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2017, Google Inc.
  * All rights reserved.
  *
@@ -31,6 +31,11 @@
  *
  */
 
+/**
+ * @module
+ * @private
+ */
+
 'use strict';
 
 var _ = require('lodash');
diff --git a/src/node/src/server.js b/src/node/src/server.js
index 1d9cc7d..ae4fcb1 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -1,5 +1,5 @@
-/*
- *
+/**
+ * @license
  * Copyright 2015, Google Inc.
  * All rights reserved.
  *
@@ -31,22 +31,6 @@
  *
  */
 
-/**
- * Server module
- *
- * This module contains all the server code for Node gRPC: both the Server
- * class itself and the method handler code for all types of methods.
- *
- * For example, to create a Server, add a service, and start it:
- *
- * var server = new server_module.Server();
- * server.addProtoService(protobuf_service_descriptor, service_implementation);
- * server.bind('address:port', server_credential);
- * server.start();
- *
- * @module
- */
-
 'use strict';
 
 var _ = require('lodash');
@@ -70,9 +54,9 @@
 
 /**
  * Handle an error on a call by sending it as a status
- * @access private
- * @param {grpc.Call} call The call to send the error on
- * @param {Object} error The error object
+ * @private
+ * @param {grpc.internal~Call} call The call to send the error on
+ * @param {(Object|Error)} error The error object
  */
 function handleError(call, error) {
   var statusMetadata = new Metadata();
@@ -104,14 +88,14 @@
 
 /**
  * Send a response to a unary or client streaming call.
- * @access private
+ * @private
  * @param {grpc.Call} call The call to respond on
  * @param {*} value The value to respond with
- * @param {function(*):Buffer=} serialize Serialization function for the
+ * @param {grpc~serialize} serialize Serialization function for the
  *     response
- * @param {Metadata=} metadata Optional trailing metadata to send with status
- * @param {number=} flags Flags for modifying how the message is sent.
- *     Defaults to 0.
+ * @param {grpc.Metadata=} metadata Optional trailing metadata to send with
+ *     status
+ * @param {number=} [flags=0] Flags for modifying how the message is sent.
  */
 function sendUnaryResponse(call, value, serialize, metadata, flags) {
   var end_batch = {};
@@ -146,7 +130,7 @@
 /**
  * Initialize a writable stream. This is used for both the writable and duplex
  * stream constructors.
- * @access private
+ * @private
  * @param {Writable} stream The stream to set up
  * @param {function(*):Buffer=} Serialization function for responses
  */
@@ -225,9 +209,9 @@
 /**
  * Initialize a readable stream. This is used for both the readable and duplex
  * stream constructors.
- * @access private
+ * @private
  * @param {Readable} stream The stream to initialize
- * @param {function(Buffer):*=} deserialize Deserialization function for
+ * @param {grpc~deserialize} deserialize Deserialization function for
  *     incoming data.
  */
 function setUpReadable(stream, deserialize) {
@@ -245,34 +229,88 @@
   });
 }
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerUnaryCall~cancelled
+ */
+
 util.inherits(ServerUnaryCall, EventEmitter);
 
-function ServerUnaryCall(call) {
+/**
+ * An EventEmitter. Used for unary calls.
+ * @constructor grpc~ServerUnaryCall
+ * @extends external:EventEmitter
+ * @param {grpc.internal~Call} call The call object associated with the request
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ */
+function ServerUnaryCall(call, metadata) {
   EventEmitter.call(this);
   this.call = call;
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerUnaryCall#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerUnaryCall#metadata
+   */
+  this.metadata = metadata;
+  /**
+   * The request message from the client
+   * @member {*} grpc~ServerUnaryCall#request
+   */
+  this.request = undefined;
 }
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerWritableStream~cancelled
+ */
+
 util.inherits(ServerWritableStream, Writable);
 
 /**
  * A stream that the server can write to. Used for calls that are streaming from
  * the server side.
- * @constructor
- * @param {grpc.Call} call The call object to send data with
- * @param {function(*):Buffer=} serialize Serialization function for writes
+ * @constructor grpc~ServerWritableStream
+ * @extends external:Writable
+ * @borrows grpc~ServerUnaryCall#sendMetadata as
+ *     grpc~ServerWritableStream#sendMetadata
+ * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerWritableStream#getPeer
+ * @param {grpc.internal~Call} call The call object to send data with
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ * @param {grpc~serialize} serialize Serialization function for writes
  */
-function ServerWritableStream(call, serialize) {
+function ServerWritableStream(call, metadata, serialize) {
   Writable.call(this, {objectMode: true});
   this.call = call;
 
   this.finished = false;
   setUpWritable(this, serialize);
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerWritableStream#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerWritableStream#metadata
+   */
+  this.metadata = metadata;
+  /**
+   * The request message from the client
+   * @member {*} grpc~ServerWritableStream#request
+   */
+  this.request = undefined;
 }
 
 /**
  * Start writing a chunk of data. This is an implementation of a method required
  * for implementing stream.Writable.
- * @access private
+ * @private
  * @param {Buffer} chunk The chunk of data to write
  * @param {string} encoding Used to pass write flags
  * @param {function(Error=)} callback Callback to indicate that the write is
@@ -312,19 +350,40 @@
 
 ServerWritableStream.prototype._write = _write;
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerReadableStream~cancelled
+ */
+
 util.inherits(ServerReadableStream, Readable);
 
 /**
  * A stream that the server can read from. Used for calls that are streaming
  * from the client side.
- * @constructor
- * @param {grpc.Call} call The call object to read data with
- * @param {function(Buffer):*=} deserialize Deserialization function for reads
+ * @constructor grpc~ServerReadableStream
+ * @extends external:Readable
+ * @borrows grpc~ServerUnaryCall#sendMetadata as
+ *     grpc~ServerReadableStream#sendMetadata
+ * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerReadableStream#getPeer
+ * @param {grpc.internal~Call} call The call object to read data with
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ * @param {grpc~deserialize} deserialize Deserialization function for reads
  */
-function ServerReadableStream(call, deserialize) {
+function ServerReadableStream(call, metadata, deserialize) {
   Readable.call(this, {objectMode: true});
   this.call = call;
   setUpReadable(this, deserialize);
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerReadableStream#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerReadableStream#metadata
+   */
+  this.metadata = metadata;
 }
 
 /**
@@ -381,22 +440,43 @@
 
 ServerReadableStream.prototype._read = _read;
 
+/**
+ * Emitted when the call has been cancelled. After this has been emitted, the
+ * call's `cancelled` property will be set to `true`.
+ * @event grpc~ServerDuplexStream~cancelled
+ */
+
 util.inherits(ServerDuplexStream, Duplex);
 
 /**
  * A stream that the server can read from or write to. Used for calls with
  * duplex streaming.
- * @constructor
- * @param {grpc.Call} call Call object to proxy
- * @param {function(*):Buffer=} serialize Serialization function for requests
- * @param {function(Buffer):*=} deserialize Deserialization function for
+ * @constructor grpc~ServerDuplexStream
+ * @extends external:Duplex
+ * @borrows grpc~ServerUnaryCall#sendMetadata as
+ *     grpc~ServerDuplexStream#sendMetadata
+ * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerDuplexStream#getPeer
+ * @param {grpc.internal~Call} call Call object to proxy
+ * @param {grpc.Metadata} metadata The request metadata from the client
+ * @param {grpc~serialize} serialize Serialization function for requests
+ * @param {grpc~deserialize} deserialize Deserialization function for
  *     responses
  */
-function ServerDuplexStream(call, serialize, deserialize) {
+function ServerDuplexStream(call, metadata, serialize, deserialize) {
   Duplex.call(this, {objectMode: true});
   this.call = call;
   setUpWritable(this, serialize);
   setUpReadable(this, deserialize);
+  /**
+   * Indicates if the call has been cancelled
+   * @member {boolean} grpc~ServerReadableStream#cancelled
+   */
+  this.cancelled = false;
+  /**
+   * The request metadata from the client
+   * @member {grpc.Metadata} grpc~ServerReadableStream#metadata
+   */
+  this.metadata = metadata;
 }
 
 ServerDuplexStream.prototype._read = _read;
@@ -404,6 +484,7 @@
 
 /**
  * Send the initial metadata for a writable stream.
+ * @alias grpc~ServerUnaryCall#sendMetadata
  * @param {Metadata} responseMetadata Metadata to send
  */
 function sendMetadata(responseMetadata) {
@@ -430,6 +511,7 @@
 
 /**
  * Get the endpoint this call/stream is connected to.
+ * @alias grpc~ServerUnaryCall#getPeer
  * @return {string} The URI of the endpoint
  */
 function getPeer() {
@@ -445,6 +527,7 @@
 /**
  * Wait for the client to close, then emit a cancelled event if the client
  * cancelled.
+ * @private
  */
 function waitForCancel() {
   /* jshint validthis: true */
@@ -468,18 +551,41 @@
 ServerDuplexStream.prototype.waitForCancel = waitForCancel;
 
 /**
+ * Callback function passed to server handlers that handle methods with unary
+ * responses.
+ * @callback grpc.Server~sendUnaryData
+ * @param {grpc~ServiceError} error An error, if the call failed
+ * @param {*} value The response value. Must be a valid argument to the
+ *     `responseSerialize` method of the method that is being handled
+ * @param {grpc.Metadata=} trailer Trailing metadata to send, if applicable
+ * @param {grpc.writeFlags=} flags Flags to modify writing the response
+ */
+
+/**
+ * User-provided method to handle unary requests on a server
+ * @callback grpc.Server~handleUnaryCall
+ * @param {grpc~ServerUnaryCall} call The call object
+ * @param {grpc.Server~sendUnaryData} callback The callback to call to respond
+ *     to the request
+ */
+
+/**
  * Fully handle a unary call
- * @access private
- * @param {grpc.Call} call The call to handle
+ * @private
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Metadata} metadata Metadata from the client
+ * @param {grpc~Server.handleUnaryCall} handler.func The handler function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
+ * @param {grpc.Metadata} metadata Metadata from the client
  */
 function handleUnary(call, handler, metadata) {
-  var emitter = new ServerUnaryCall(call);
+  var emitter = new ServerUnaryCall(call, metadata);
   emitter.on('error', function(error) {
     handleError(call, error);
   });
-  emitter.metadata = metadata;
   emitter.waitForCancel();
   var batch = {};
   batch[grpc.opType.RECV_MESSAGE] = true;
@@ -512,16 +618,27 @@
 }
 
 /**
+ * User provided method to handle server streaming methods on the server.
+ * @callback grpc.Server~handleServerStreamingCall
+ * @param {grpc~ServerWritableStream} call The call object
+ */
+
+/**
  * Fully handle a server streaming call
- * @access private
- * @param {grpc.Call} call The call to handle
+ * @private
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Metadata} metadata Metadata from the client
+ * @param {grpc~Server.handleServerStreamingCall} handler.func The handler
+ *     function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
+ * @param {grpc.Metadata} metadata Metadata from the client
  */
 function handleServerStreaming(call, handler, metadata) {
-  var stream = new ServerWritableStream(call, handler.serialize);
+  var stream = new ServerWritableStream(call, metadata, handler.serialize);
   stream.waitForCancel();
-  stream.metadata = metadata;
   var batch = {};
   batch[grpc.opType.RECV_MESSAGE] = true;
   call.startBatch(batch, function(err, result) {
@@ -541,19 +658,32 @@
 }
 
 /**
+ * User provided method to handle client streaming methods on the server.
+ * @callback grpc.Server~handleClientStreamingCall
+ * @param {grpc~ServerReadableStream} call The call object
+ * @param {grpc.Server~sendUnaryData} callback The callback to call to respond
+ *     to the request
+ */
+
+/**
  * Fully handle a client streaming call
  * @access private
- * @param {grpc.Call} call The call to handle
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
- * @param {Metadata} metadata Metadata from the client
+ * @param {grpc~Server.handleClientStreamingCall} handler.func The handler
+ *     function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
+ * @param {grpc.Metadata} metadata Metadata from the client
  */
 function handleClientStreaming(call, handler, metadata) {
-  var stream = new ServerReadableStream(call, handler.deserialize);
+  var stream = new ServerReadableStream(call, metadata, handler.deserialize);
   stream.on('error', function(error) {
     handleError(call, error);
   });
   stream.waitForCancel();
-  stream.metadata = metadata;
   handler.func(stream, function(err, value, trailer, flags) {
     stream.terminate();
     if (err) {
@@ -568,17 +698,28 @@
 }
 
 /**
+ * User provided method to handle bidirectional streaming calls on the server.
+ * @callback grpc.Server~handleBidiStreamingCall
+ * @param {grpc~ServerDuplexStream} call The call object
+ */
+
+/**
  * Fully handle a bidirectional streaming call
- * @access private
- * @param {grpc.Call} call The call to handle
+ * @private
+ * @param {grpc.internal~Call} call The call to handle
  * @param {Object} handler Request handler object for the method that was called
+ * @param {grpc~Server.handleBidiStreamingCall} handler.func The handler
+ *     function
+ * @param {grpc~deserialize} handler.deserialize The deserialization function
+ *     for request data
+ * @param {grpc~serialize} handler.serialize The serialization function for
+ *     response data
  * @param {Metadata} metadata Metadata from the client
  */
 function handleBidiStreaming(call, handler, metadata) {
-  var stream = new ServerDuplexStream(call, handler.serialize,
+  var stream = new ServerDuplexStream(call, metadata, handler.serialize,
                                       handler.deserialize);
   stream.waitForCancel();
-  stream.metadata = metadata;
   handler.func(stream);
 }
 
@@ -592,96 +733,90 @@
 /**
  * Constructs a server object that stores request handlers and delegates
  * incoming requests to those handlers
+ * @memberof grpc
  * @constructor
  * @param {Object=} options Options that should be passed to the internal server
  *     implementation
+ * @example
+ * var server = new grpc.Server();
+ * server.addProtoService(protobuf_service_descriptor, service_implementation);
+ * server.bind('address:port', server_credential);
+ * server.start();
  */
 function Server(options) {
   this.handlers = {};
-  var handlers = this.handlers;
   var server = new grpc.Server(options);
   this._server = server;
   this.started = false;
-  /**
-   * Start the server and begin handling requests
-   * @this Server
-   */
-  this.start = function() {
-    if (this.started) {
-      throw new Error('Server is already running');
-    }
-    this.started = true;
-    server.start();
-    /**
-     * Handles the SERVER_RPC_NEW event. If there is a handler associated with
-     * the requested method, use that handler to respond to the request. Then
-     * wait for the next request
-     * @param {grpc.Event} event The event to handle with tag SERVER_RPC_NEW
-     */
-    function handleNewCall(err, event) {
-      if (err) {
-        return;
-      }
-      var details = event.new_call;
-      var call = details.call;
-      var method = details.method;
-      var metadata = Metadata._fromCoreRepresentation(details.metadata);
-      if (method === null) {
-        return;
-      }
-      server.requestCall(handleNewCall);
-      var handler;
-      if (handlers.hasOwnProperty(method)) {
-        handler = handlers[method];
-      } else {
-        var batch = {};
-        batch[grpc.opType.SEND_INITIAL_METADATA] =
-            (new Metadata())._getCoreRepresentation();
-        batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
-          code: constants.status.UNIMPLEMENTED,
-          details: '',
-          metadata: {}
-        };
-        batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
-        call.startBatch(batch, function() {});
-        return;
-      }
-      streamHandlers[handler.type](call, handler, metadata);
-    }
-    server.requestCall(handleNewCall);
-  };
-
-  /**
-   * Gracefully shuts down the server. The server will stop receiving new calls,
-   * and any pending calls will complete. The callback will be called when all
-   * pending calls have completed and the server is fully shut down. This method
-   * is idempotent with itself and forceShutdown.
-   * @param {function()} callback The shutdown complete callback
-   */
-  this.tryShutdown = function(callback) {
-    server.tryShutdown(callback);
-  };
-
-  /**
-   * Forcibly shuts down the server. The server will stop receiving new calls
-   * and cancel all pending calls. When it returns, the server has shut down.
-   * This method is idempotent with itself and tryShutdown, and it will trigger
-   * any outstanding tryShutdown callbacks.
-   */
-  this.forceShutdown = function() {
-    server.forceShutdown();
-  };
 }
 
 /**
+ * Start the server and begin handling requests
+ */
+Server.prototype.start = function() {
+  if (this.started) {
+    throw new Error('Server is already running');
+  }
+  var self = this;
+  this.started = true;
+  this._server.start();
+  /**
+   * Handles the SERVER_RPC_NEW event. If there is a handler associated with
+   * the requested method, use that handler to respond to the request. Then
+   * wait for the next request
+   * @param {grpc.internal~Event} event The event to handle with tag
+   *     SERVER_RPC_NEW
+   */
+  function handleNewCall(err, event) {
+    if (err) {
+      return;
+    }
+    var details = event.new_call;
+    var call = details.call;
+    var method = details.method;
+    var metadata = Metadata._fromCoreRepresentation(details.metadata);
+    if (method === null) {
+      return;
+    }
+    self._server.requestCall(handleNewCall);
+    var handler;
+    if (self.handlers.hasOwnProperty(method)) {
+      handler = self.handlers[method];
+    } else {
+      var batch = {};
+      batch[grpc.opType.SEND_INITIAL_METADATA] =
+          (new Metadata())._getCoreRepresentation();
+      batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
+        code: constants.status.UNIMPLEMENTED,
+        details: '',
+        metadata: {}
+      };
+      batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
+      call.startBatch(batch, function() {});
+      return;
+    }
+    streamHandlers[handler.type](call, handler, metadata);
+  }
+  this._server.requestCall(handleNewCall);
+};
+
+/**
+ * Unified type for application handlers for all types of calls
+ * @typedef {(grpc.Server~handleUnaryCall
+ *            |grpc.Server~handleClientStreamingCall
+ *            |grpc.Server~handleServerStreamingCall
+ *            |grpc.Server~handleBidiStreamingCall)} grpc.Server~handleCall
+ */
+
+/**
  * Registers a handler to handle the named method. Fails if there already is
  * a handler for the given method. Returns true on success
  * @param {string} name The name of the method that the provided function should
  *     handle/respond to.
- * @param {function} handler Function that takes a stream of request values and
- *     returns a stream of response values
- * @param {function(*):Buffer} serialize Serialization function for responses
- * @param {function(Buffer):*} deserialize Deserialization function for requests
+ * @param {grpc.Server~handleCall} handler Function that takes a stream of
+ *     request values and returns a stream of response values
+ * @param {grpc~serialize} serialize Serialization function for responses
+ * @param {grpc~deserialize} deserialize Deserialization function for requests
  * @param {string} type The streaming type of method that this handles
  * @return {boolean} True if the handler was set. False if a handler was already
  *     set for that name.
@@ -700,6 +835,27 @@
   return true;
 };
 
+/**
+ * Gracefully shuts down the server. The server will stop receiving new calls,
+ * and any pending calls will complete. The callback will be called when all
+ * pending calls have completed and the server is fully shut down. This method
+ * is idempotent with itself and forceShutdown.
+ * @param {function()} callback The shutdown complete callback
+ */
+Server.prototype.tryShutdown = function(callback) {
+  this._server.tryShutdown(callback);
+};
+
+/**
+ * Forcibly shuts down the server. The server will stop receiving new calls
+ * and cancel all pending calls. When it returns, the server has shut down.
+ * This method is idempotent with itself and tryShutdown, and it will trigger
+ * any outstanding tryShutdown callbacks.
+ */
+Server.prototype.forceShutdown = function() {
+  this._server.forceShutdown();
+};
+
 var unimplementedStatusResponse = {
   code: constants.status.UNIMPLEMENTED,
   details: 'The server does not implement this method'
@@ -721,13 +877,10 @@
 };
 
 /**
- * Add a service to the server, with a corresponding implementation. If you are
- * generating this from a proto file, you should instead use
- * addProtoService.
- * @param {Object<String, *>} service The service descriptor, as
- *     {@link module:src/common.getProtobufServiceAttrs} returns
- * @param {Object<String, function>} implementation Map of method names to
- *     method implementation for the provided service.
+ * Add a service to the server, with a corresponding implementation.
+ * @param {grpc~ServiceDefinition} service The service descriptor
+ * @param {Object<String, grpc.Server~handleCall>} implementation Map of method
+ *     names to method implementation for the provided service.
  */
 Server.prototype.addService = function(service, implementation) {
   if (!_.isObject(service) || !_.isObject(implementation)) {
@@ -788,10 +941,10 @@
 
 /**
  * Add a proto service to the server, with a corresponding implementation
- * @deprecated Use grpc.load and Server#addService instead
+ * @deprecated Use {@link grpc.Server#addService} instead
  * @param {Protobuf.Reflect.Service} service The proto service descriptor
- * @param {Object<String, function>} implementation Map of method names to
- *     method implementation for the provided service.
+ * @param {Object<String, grpc.Server~handleCall>} implementation Map of method
+ *     names to method implementation for the provided service.
  */
 Server.prototype.addProtoService = function(service, implementation) {
   var options;
@@ -815,10 +968,11 @@
 };
 
 /**
- * Binds the server to the given port, with SSL enabled if creds is given
+ * Binds the server to the given port, with SSL disabled if creds is an
+ * insecure credentials object
  * @param {string} port The port that the server should bind on, in the format
  *     "address:port"
- * @param {ServerCredentials=} creds Server credential object to be used for
+ * @param {grpc.ServerCredentials} creds Server credential object to be used for
  *     SSL. Pass an insecure credentials object for an insecure port.
  */
 Server.prototype.bind = function(port, creds) {
@@ -828,7 +982,4 @@
   return this._server.addHttp2Port(port, creds);
 };
 
-/**
- * @see module:src/server~Server
- */
 exports.Server = Server;
diff --git a/src/node/test/common_test.js b/src/node/test/common_test.js
index b7c2c6a..db80207 100644
--- a/src/node/test/common_test.js
+++ b/src/node/test/common_test.js
@@ -100,7 +100,6 @@
     var longNumDeserialize = deserializeCls(messages_proto.LongValues,
                                             num_options);
     var serialized = longSerialize({int_64: pos_value});
-    console.log(longDeserialize(serialized));
     assert.strictEqual(typeof longDeserialize(serialized).int_64, 'string');
     /* With the longsAsStrings option disabled, long values are represented as
      * objects with 3 keys: low, high, and unsigned */
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index d2f0511..f8eaf62 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -1110,6 +1110,18 @@
         done();
       });
     });
+    it('after the call has fully completed', function(done) {
+      var peer;
+      var call = client.unary({error: false}, function(err, data) {
+        assert.ifError(err);
+        setImmediate(function() {
+          assert.strictEqual(peer, call.getPeer());
+          done();
+        });
+      });
+      peer = call.getPeer();
+      assert.strictEqual(typeof peer, 'string');
+    });
   });
 });
 describe('Call propagation', function() {
@@ -1322,14 +1334,14 @@
   });
   it('Should correctly cancel a unary call', function(done) {
     var call = client.div({'divisor': 0, 'dividend': 0}, function(err, resp) {
-      assert.strictEqual(err.code, surface_client.status.CANCELLED);
+      assert.strictEqual(err.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
   });
   it('Should correctly cancel a client stream call', function(done) {
     var call = client.sum(function(err, resp) {
-      assert.strictEqual(err.code, surface_client.status.CANCELLED);
+      assert.strictEqual(err.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
@@ -1338,7 +1350,7 @@
     var call = client.fib({'limit': 5});
     call.on('data', function() {});
     call.on('error', function(error) {
-      assert.strictEqual(error.code, surface_client.status.CANCELLED);
+      assert.strictEqual(error.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
@@ -1347,9 +1359,22 @@
     var call = client.divMany();
     call.on('data', function() {});
     call.on('error', function(error) {
-      assert.strictEqual(error.code, surface_client.status.CANCELLED);
+      assert.strictEqual(error.code, grpc.status.CANCELLED);
       done();
     });
     call.cancel();
   });
+  it('Should be idempotent', function(done) {
+    var call = client.div({'divisor': 0, 'dividend': 0}, function(err, resp) {
+      assert.strictEqual(err.code, grpc.status.CANCELLED);
+      // Call asynchronously to try cancelling after call is fully completed
+      setImmediate(function() {
+        assert.doesNotThrow(function() {
+          call.cancel();
+        });
+        done();
+      });
+    });
+    call.cancel();
+  });
 });
diff --git a/src/node/tools/package.json b/src/node/tools/package.json
index a81aa87..f4f72a4 100644
--- a/src/node/tools/package.json
+++ b/src/node/tools/package.json
@@ -1,6 +1,6 @@
 {
   "name": "grpc-tools",
-  "version": "1.4.0-dev",
+  "version": "1.4.0-pre1",
   "author": "Google Inc.",
   "description": "Tools for developing with gRPC on Node.js",
   "homepage": "http://www.grpc.io/",
diff --git "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec" "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
index 2f29058..09f3303 100644
--- "a/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
+++ "b/src/objective-c/\041ProtoCompiler-gRPCPlugin.podspec"
@@ -42,7 +42,7 @@
   # exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
   # before them.
   s.name     = '!ProtoCompiler-gRPCPlugin'
-  v = '1.4.0-dev'
+  v = '1.4.0-pre1'
   s.version  = v
   s.summary  = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.'
   s.description = <<-DESC
diff --git a/src/objective-c/GRPCClient/private/version.h b/src/objective-c/GRPCClient/private/version.h
index c846f42..25a232a 100644
--- a/src/objective-c/GRPCClient/private/version.h
+++ b/src/objective-c/GRPCClient/private/version.h
@@ -38,4 +38,4 @@
 // `tools/buildgen/generate_projects.sh`.
 
 
-#define GRPC_OBJC_VERSION_STRING @"1.4.0-dev"
+#define GRPC_OBJC_VERSION_STRING @"1.4.0-pre1"
diff --git a/src/php/ext/grpc/version.h b/src/php/ext/grpc/version.h
index 993ef2d..0d5d36d 100644
--- a/src/php/ext/grpc/version.h
+++ b/src/php/ext/grpc/version.h
@@ -35,6 +35,6 @@
 #ifndef VERSION_H
 #define VERSION_H
 
-#define PHP_GRPC_VERSION "1.4.0"
+#define PHP_GRPC_VERSION "1.4.0RC1"
 
 #endif /* VERSION_H */
diff --git a/src/python/grpcio/grpc/_channel.py b/src/python/grpcio/grpc/_channel.py
index 4316449..012ed8e 100644
--- a/src/python/grpcio/grpc/_channel.py
+++ b/src/python/grpcio/grpc/_channel.py
@@ -786,7 +786,7 @@
 class _ChannelConnectivityState(object):
 
     def __init__(self, channel):
-        self.lock = threading.Lock()
+        self.lock = threading.RLock()
         self.channel = channel
         self.polling = False
         self.connectivity = None
@@ -926,6 +926,11 @@
         self._call_state = _ChannelCallState(self._channel)
         self._connectivity_state = _ChannelConnectivityState(self._channel)
 
+        # TODO(https://github.com/grpc/grpc/issues/9884)
+        # Temporary work around UNAVAILABLE issues
+        # Remove this once c-core has retry support
+        _subscribe(self._connectivity_state, lambda *args: None, None)
+
     def subscribe(self, callback, try_to_connect=None):
         _subscribe(self._connectivity_state, callback, try_to_connect)
 
diff --git a/src/python/grpcio/grpc/_grpcio_metadata.py b/src/python/grpcio/grpc/_grpcio_metadata.py
index a0cb0dd..fcfd197 100644
--- a/src/python/grpcio/grpc/_grpcio_metadata.py
+++ b/src/python/grpcio/grpc/_grpcio_metadata.py
@@ -29,4 +29,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc/_grpcio_metadata.py.template`!!!
 
-__version__ = """1.4.0.dev0"""
+__version__ = """1.4.0rc1"""
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 9770301..5f80754 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -313,6 +313,7 @@
   'src/core/ext/census/grpc_filter.c',
   'src/core/ext/census/grpc_plugin.c',
   'src/core/ext/census/initialize.c',
+  'src/core/ext/census/intrusive_hash_map.c',
   'src/core/ext/census/mlog.c',
   'src/core/ext/census/operation.c',
   'src/core/ext/census/placeholders.c',
diff --git a/src/python/grpcio/grpc_version.py b/src/python/grpcio/grpc_version.py
index ea4bc7b..31b85e5 100644
--- a/src/python/grpcio/grpc_version.py
+++ b/src/python/grpcio/grpc_version.py
@@ -29,4 +29,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc_version.py.template`!!!
 
-VERSION='1.4.0.dev0'
+VERSION='1.4.0rc1'
diff --git a/src/python/grpcio_health_checking/grpc_version.py b/src/python/grpcio_health_checking/grpc_version.py
index 26aa555..53d798f 100644
--- a/src/python/grpcio_health_checking/grpc_version.py
+++ b/src/python/grpcio_health_checking/grpc_version.py
@@ -29,4 +29,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
 
-VERSION='1.4.0.dev0'
+VERSION='1.4.0rc1'
diff --git a/src/python/grpcio_reflection/grpc_reflection/v1alpha/reflection.py b/src/python/grpcio_reflection/grpc_reflection/v1alpha/reflection.py
index cd896f3..0f399f8 100644
--- a/src/python/grpcio_reflection/grpc_reflection/v1alpha/reflection.py
+++ b/src/python/grpcio_reflection/grpc_reflection/v1alpha/reflection.py
@@ -152,4 +152,4 @@
       pool: DescriptorPool object to use (descriptor_pool.Default() if None).
     """
     reflection_pb2_grpc.add_ServerReflectionServicer_to_server(
-        ReflectionServicer(service_names), server, pool)
+        ReflectionServicer(service_names, pool), server)
diff --git a/src/python/grpcio_reflection/grpc_version.py b/src/python/grpcio_reflection/grpc_version.py
index 978d6b4..25ee280 100644
--- a/src/python/grpcio_reflection/grpc_version.py
+++ b/src/python/grpcio_reflection/grpc_version.py
@@ -29,4 +29,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_reflection/grpc_version.py.template`!!!
 
-VERSION='1.4.0.dev0'
+VERSION='1.4.0rc1'
diff --git a/src/python/grpcio_tests/grpc_version.py b/src/python/grpcio_tests/grpc_version.py
index 5f0b084..ea9d307 100644
--- a/src/python/grpcio_tests/grpc_version.py
+++ b/src/python/grpcio_tests/grpc_version.py
@@ -29,4 +29,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_tests/grpc_version.py.template`!!!
 
-VERSION='1.4.0.dev0'
+VERSION='1.4.0rc1'
diff --git a/src/python/grpcio_tests/tests/tests.json b/src/python/grpcio_tests/tests/tests.json
index 9f7587f..126e8ac 100644
--- a/src/python/grpcio_tests/tests/tests.json
+++ b/src/python/grpcio_tests/tests/tests.json
@@ -32,6 +32,7 @@
   "unit._invocation_defects_test.InvocationDefectsTest",
   "unit._metadata_code_details_test.MetadataCodeDetailsTest",
   "unit._metadata_test.MetadataTest",
+  "unit._reconnect_test.ReconnectTest",
   "unit._resource_exhausted_test.ResourceExhaustedTest",
   "unit._rpc_test.RPCTest",
   "unit._sanity._sanity_test.Sanity",
diff --git a/src/python/grpcio_tests/tests/unit/_reconnect_test.py b/src/python/grpcio_tests/tests/unit/_reconnect_test.py
new file mode 100644
index 0000000..6c31647
--- /dev/null
+++ b/src/python/grpcio_tests/tests/unit/_reconnect_test.py
@@ -0,0 +1,70 @@
+# Copyright 2017, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""Tests that a channel will reconnect if a connection is dropped"""
+
+import unittest
+
+import grpc
+from grpc.framework.foundation import logging_pool
+
+from tests.unit.framework.common import test_constants
+
+_REQUEST = b'\x00\x00\x00'
+_RESPONSE = b'\x00\x00\x01'
+
+_UNARY_UNARY = '/test/UnaryUnary'
+
+
+def _handle_unary_unary(unused_request, unused_servicer_context):
+    return _RESPONSE
+
+
+class ReconnectTest(unittest.TestCase):
+
+    def test_reconnect(self):
+        server_pool = logging_pool.pool(test_constants.THREAD_CONCURRENCY)
+        handler = grpc.method_handlers_generic_handler('test', {
+            'UnaryUnary':
+            grpc.unary_unary_rpc_method_handler(_handle_unary_unary)
+        })
+        server = grpc.server(server_pool, (handler,))
+        port = server.add_insecure_port('[::]:0')
+        server.start()
+        channel = grpc.insecure_channel('localhost:%d' % port)
+        multi_callable = channel.unary_unary(_UNARY_UNARY)
+        self.assertEqual(_RESPONSE, multi_callable(_REQUEST))
+        server.stop(None)
+        server = grpc.server(server_pool, (handler,))
+        server.add_insecure_port('[::]:{}'.format(port))
+        server.start()
+        self.assertEqual(_RESPONSE, multi_callable(_REQUEST))
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/src/ruby/end2end/channel_closing_driver.rb b/src/ruby/end2end/channel_closing_driver.rb
index d3e5373..bed8c43 100755
--- a/src/ruby/end2end/channel_closing_driver.rb
+++ b/src/ruby/end2end/channel_closing_driver.rb
@@ -61,6 +61,11 @@
       'channel is closed while connectivity is watched'
   end
 
+  client_exit_code = $CHILD_STATUS
+  if client_exit_code != 0
+    fail "channel closing client failed, exit code #{client_exit_code}"
+  end
+
   server_runner.stop
 end
 
diff --git a/src/ruby/end2end/channel_state_driver.rb b/src/ruby/end2end/channel_state_driver.rb
index 80fb628..9910076 100755
--- a/src/ruby/end2end/channel_state_driver.rb
+++ b/src/ruby/end2end/channel_state_driver.rb
@@ -58,6 +58,9 @@
            'It likely hangs when ended abruptly'
   end
 
+  # The interrupt in the child process should cause it to
+  # exit a non-zero status, so don't check it here.
+  # This test mainly tries to catch deadlock.
   server_runner.stop
 end
 
diff --git a/src/ruby/end2end/grpc_class_init_client.rb b/src/ruby/end2end/grpc_class_init_client.rb
index ee79292..e73ca76 100755
--- a/src/ruby/end2end/grpc_class_init_client.rb
+++ b/src/ruby/end2end/grpc_class_init_client.rb
@@ -34,44 +34,110 @@
 
 require_relative './end2end_common'
 
-def main
-  grpc_class = ''
-  OptionParser.new do |opts|
-    opts.on('--grpc_class=P', String) do |p|
-      grpc_class = p
+def construct_many(test_proc)
+  thds = []
+  4.times do
+    thds << Thread.new do
+      20.times do
+        test_proc.call
+      end
     end
-  end.parse!
+  end
+  20.times do
+    test_proc.call
+  end
+  thds.each(&:join)
+end
 
-  test_proc = nil
+def run_gc_stress_test(test_proc)
+  GC.disable
+  construct_many(test_proc)
 
+  GC.enable
+  construct_many(test_proc)
+
+  GC.start(full_mark: true, immediate_sweep: true)
+  construct_many(test_proc)
+end
+
+def run_concurrency_stress_test(test_proc)
+  100.times do
+    Thread.new do
+      test_proc.call
+    end
+  end
+
+  test_proc.call
+
+  fail 'exception thrown while child thread initing class'
+end
+
+# default (no gc_stress and no concurrency_stress)
+def run_default_test(test_proc)
+  thd = Thread.new do
+    test_proc.call
+  end
+  test_proc.call
+  thd.join
+end
+
+def get_test_proc(grpc_class)
   case grpc_class
   when 'channel'
-    test_proc = proc do
+    return proc do
       GRPC::Core::Channel.new('dummy_host', nil, :this_channel_is_insecure)
     end
   when 'server'
-    test_proc = proc do
+    return proc do
       GRPC::Core::Server.new({})
     end
   when 'channel_credentials'
-    test_proc = proc do
+    return proc do
       GRPC::Core::ChannelCredentials.new
     end
   when 'call_credentials'
-    test_proc = proc do
+    return proc do
       GRPC::Core::CallCredentials.new(proc { |noop| noop })
     end
   when 'compression_options'
-    test_proc = proc do
+    return proc do
       GRPC::Core::CompressionOptions.new
     end
   else
     fail "bad --grpc_class=#{grpc_class} param"
   end
+end
 
-  th = Thread.new { test_proc.call }
-  test_proc.call
-  th.join
+def main
+  grpc_class = ''
+  stress_test = ''
+  OptionParser.new do |opts|
+    opts.on('--grpc_class=P', String) do |p|
+      grpc_class = p
+    end
+    opts.on('--stress_test=P') do |p|
+      stress_test = p
+    end
+  end.parse!
+
+  test_proc = get_test_proc(grpc_class)
+
+  # the different test configs need to be ran
+  # in separate processes, since each one tests
+  # clean shutdown in a different way
+  case stress_test
+  when 'gc'
+    p 'run gc stress'
+    run_gc_stress_test(test_proc)
+  when 'concurrency'
+    p 'run concurrency stress'
+    run_concurrency_stress_test(test_proc)
+  when ''
+    p 'run default'
+    run_default_test(test_proc)
+  else
+    fail "bad --stress_test=#{stress_test} param"
+  end
 end
 
 main
diff --git a/src/ruby/end2end/grpc_class_init_driver.rb b/src/ruby/end2end/grpc_class_init_driver.rb
index 764d029..c65ed54 100755
--- a/src/ruby/end2end/grpc_class_init_driver.rb
+++ b/src/ruby/end2end/grpc_class_init_driver.rb
@@ -38,29 +38,40 @@
                             call_credentials
                             compression_options )
 
-  native_grpc_classes.each do |grpc_class|
-    STDERR.puts 'start client'
-    this_dir = File.expand_path(File.dirname(__FILE__))
-    client_path = File.join(this_dir, 'grpc_class_init_client.rb')
-    client_pid = Process.spawn(RbConfig.ruby,
-                               client_path,
-                               "--grpc_class=#{grpc_class}")
-    begin
-      Timeout.timeout(10) do
-        Process.wait(client_pid)
-      end
-    rescue Timeout::Error
-      STDERR.puts "timeout waiting for client pid #{client_pid}"
-      Process.kill('SIGKILL', client_pid)
-      Process.wait(client_pid)
-      STDERR.puts 'killed client child'
-      raise 'Timed out waiting for client process. ' \
-        'It likely hangs when the first constructed gRPC object has ' \
-        "type: #{grpc_class}"
-    end
+  # there is room for false positives in this test,
+  # do a few runs for each config
+  4.times do
+    native_grpc_classes.each do |grpc_class|
+      ['', 'gc', 'concurrency'].each do |stress_test_type|
+        STDERR.puts 'start client'
+        this_dir = File.expand_path(File.dirname(__FILE__))
+        client_path = File.join(this_dir, 'grpc_class_init_client.rb')
+        client_pid = Process.spawn(RbConfig.ruby,
+                                   client_path,
+                                   "--grpc_class=#{grpc_class}",
+                                   "--stress_test=#{stress_test_type}")
+        begin
+          Timeout.timeout(10) do
+            Process.wait(client_pid)
+          end
+        rescue Timeout::Error
+          STDERR.puts "timeout waiting for client pid #{client_pid}"
+          Process.kill('SIGKILL', client_pid)
+          Process.wait(client_pid)
+          STDERR.puts 'killed client child'
+          raise 'Timed out waiting for client process. ' \
+            'It likely hangs when the first constructed gRPC object has ' \
+            "type: #{grpc_class}"
+        end
 
-    client_exit_code = $CHILD_STATUS
-    fail "client failed, exit code #{client_exit_code}" if client_exit_code != 0
+        client_exit_code = $CHILD_STATUS
+        # concurrency stress test type is expected to exit with a
+        # non-zero status due to an exception being raised
+        if client_exit_code != 0 && stress_test_type != 'concurrency'
+          fail "client failed, exit code #{client_exit_code}"
+        end
+      end
+    end
   end
 end
 
diff --git a/src/ruby/end2end/multiple_killed_watching_threads_driver.rb b/src/ruby/end2end/multiple_killed_watching_threads_driver.rb
new file mode 100755
index 0000000..206ec8e
--- /dev/null
+++ b/src/ruby/end2end/multiple_killed_watching_threads_driver.rb
@@ -0,0 +1,63 @@
+#!/usr/bin/env ruby
+
+# Copyright 2016, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+require_relative './end2end_common'
+
+Thread.abort_on_exception = true
+
+include GRPC::Core::ConnectivityStates
+
+def watch_state(ch)
+  thd = Thread.new do
+    state = ch.connectivity_state(false)
+    fail "non-idle state: #{state}" unless state == IDLE
+    ch.watch_connectivity_state(IDLE, Time.now + 360)
+  end
+  sleep 0.1
+  thd.kill
+end
+
+def main
+  channels = []
+  10.times do
+    ch = GRPC::Core::Channel.new('dummy_host',
+                                 nil, :this_channel_is_insecure)
+    watch_state(ch)
+    channels << ch
+  end
+
+  # checking state should still be safe to call
+  channels.each do |c|
+    fail unless c.connectivity_state(false) == FATAL_FAILURE
+  end
+end
+
+main
diff --git a/src/ruby/end2end/sig_int_during_channel_watch_client.rb b/src/ruby/end2end/sig_int_during_channel_watch_client.rb
index 389fc5b..0c6a374 100755
--- a/src/ruby/end2end/sig_int_during_channel_watch_client.rb
+++ b/src/ruby/end2end/sig_int_during_channel_watch_client.rb
@@ -46,6 +46,8 @@
     end
   end.parse!
 
+  trap('SIGINT') { exit 0 }
+
   thd = Thread.new do
     child_thread_channel = GRPC::Core::Channel.new("localhost:#{server_port}",
                                                    {},
diff --git a/src/ruby/end2end/sig_int_during_channel_watch_driver.rb b/src/ruby/end2end/sig_int_during_channel_watch_driver.rb
index 670cda0..79a8c13 100755
--- a/src/ruby/end2end/sig_int_during_channel_watch_driver.rb
+++ b/src/ruby/end2end/sig_int_during_channel_watch_driver.rb
@@ -63,6 +63,11 @@
       'SIGINT is sent while there is an active connectivity_state call'
   end
 
+  client_exit_code = $CHILD_STATUS
+  if client_exit_code != 0
+    fail "sig_int_during_channel_watch_client failed: #{client_exit_code}"
+  end
+
   server_runner.stop
 end
 
diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c
index a802183..f653884 100644
--- a/src/ruby/ext/grpc/rb_channel.c
+++ b/src/ruby/ext/grpc/rb_channel.c
@@ -68,29 +68,53 @@
 /* Used during the conversion of a hash to channel args during channel setup */
 static VALUE grpc_rb_cChannelArgs;
 
+typedef struct bg_watched_channel {
+  grpc_channel *channel;
+  // these fields must only be accessed under global_connection_polling_mu
+  struct bg_watched_channel *next;
+  int channel_destroyed;
+  int refcount;
+} bg_watched_channel;
+
 /* grpc_rb_channel wraps a grpc_channel. */
 typedef struct grpc_rb_channel {
   VALUE credentials;
 
-  /* The actual channel */
-  grpc_channel *wrapped;
-  int request_safe_destroy;
-  int safe_to_destroy;
-  grpc_connectivity_state current_connectivity_state;
-
-  int mu_init_done;
-  int abort_watch_connectivity_state;
-  gpr_mu channel_mu;
-  gpr_cv channel_cv;
+  /* The actual channel (protected in a wrapper to tell when it's safe to
+   * destroy) */
+  bg_watched_channel *bg_wrapped;
 } grpc_rb_channel;
 
-/* Forward declarations of functions involved in temporary fix to
- * https://github.com/grpc/grpc/issues/9941 */
+typedef enum { CONTINUOUS_WATCH, WATCH_STATE_API } watch_state_op_type;
+
+typedef struct watch_state_op {
+  watch_state_op_type op_type;
+  // from event.success
+  union {
+    struct {
+      int success;
+      // has been called back due to a cq next call
+      int called_back;
+    } api_callback_args;
+    struct {
+      bg_watched_channel *bg;
+    } continuous_watch_callback_args;
+  } op;
+} watch_state_op;
+
+static bg_watched_channel *bg_watched_channel_list_head = NULL;
+
 static void grpc_rb_channel_try_register_connection_polling(
-    grpc_rb_channel *wrapper);
-static void grpc_rb_channel_safe_destroy(grpc_rb_channel *wrapper);
+    bg_watched_channel *bg);
 static void *wait_until_channel_polling_thread_started_no_gil(void *);
 static void wait_until_channel_polling_thread_started_unblocking_func(void *);
+static void *channel_init_try_register_connection_polling_without_gil(
+    void *arg);
+
+typedef struct channel_init_try_register_stack {
+  grpc_channel *channel;
+  grpc_rb_channel *wrapper;
+} channel_init_try_register_stack;
 
 static grpc_completion_queue *channel_polling_cq;
 static gpr_mu global_connection_polling_mu;
@@ -98,6 +122,42 @@
 static int abort_channel_polling = 0;
 static int channel_polling_thread_started = 0;
 
+static int bg_watched_channel_list_lookup(bg_watched_channel *bg);
+static bg_watched_channel *bg_watched_channel_list_create_and_add(
+    grpc_channel *channel);
+static void bg_watched_channel_list_free_and_remove(bg_watched_channel *bg);
+static void run_poll_channels_loop_unblocking_func(void *arg);
+
+// Needs to be called under global_connection_polling_mu
+static void grpc_rb_channel_watch_connection_state_op_complete(
+    watch_state_op *op, int success) {
+  GPR_ASSERT(!op->op.api_callback_args.called_back);
+  op->op.api_callback_args.called_back = 1;
+  op->op.api_callback_args.success = success;
+  // wake up the watch API call thats waiting on this op
+  gpr_cv_broadcast(&global_connection_polling_cv);
+}
+
+/* Avoids destroying a channel twice. */
+static void grpc_rb_channel_safe_destroy(bg_watched_channel *bg) {
+  gpr_mu_lock(&global_connection_polling_mu);
+  GPR_ASSERT(bg_watched_channel_list_lookup(bg));
+  if (!bg->channel_destroyed) {
+    grpc_channel_destroy(bg->channel);
+    bg->channel_destroyed = 1;
+  }
+  bg->refcount--;
+  if (bg->refcount == 0) {
+    bg_watched_channel_list_free_and_remove(bg);
+  }
+  gpr_mu_unlock(&global_connection_polling_mu);
+}
+
+static void *channel_safe_destroy_without_gil(void *arg) {
+  grpc_rb_channel_safe_destroy((bg_watched_channel *)arg);
+  return NULL;
+}
+
 /* Destroys Channel instances. */
 static void grpc_rb_channel_free(void *p) {
   grpc_rb_channel *ch = NULL;
@@ -106,14 +166,13 @@
   };
   ch = (grpc_rb_channel *)p;
 
-  if (ch->wrapped != NULL) {
-    grpc_rb_channel_safe_destroy(ch);
-    ch->wrapped = NULL;
-  }
-
-  if (ch->mu_init_done) {
-    gpr_mu_destroy(&ch->channel_mu);
-    gpr_cv_destroy(&ch->channel_cv);
+  if (ch->bg_wrapped != NULL) {
+    /* assumption made here: it's ok to directly gpr_mu_lock the global
+     * connection polling mutex becuse we're in a finalizer,
+     * and we can count on this thread to not be interrupted or
+     * yield the gil. */
+    grpc_rb_channel_safe_destroy(ch->bg_wrapped);
+    ch->bg_wrapped = NULL;
   }
 
   xfree(p);
@@ -146,7 +205,7 @@
 /* Allocates grpc_rb_channel instances. */
 static VALUE grpc_rb_channel_alloc(VALUE cls) {
   grpc_rb_channel *wrapper = ALLOC(grpc_rb_channel);
-  wrapper->wrapped = NULL;
+  wrapper->bg_wrapped = NULL;
   wrapper->credentials = Qnil;
   return TypedData_Wrap_Struct(cls, &grpc_channel_data_type, wrapper);
 }
@@ -168,18 +227,21 @@
   grpc_channel_credentials *creds = NULL;
   char *target_chars = NULL;
   grpc_channel_args args;
+  channel_init_try_register_stack stack;
+  int stop_waiting_for_thread_start = 0;
   MEMZERO(&args, grpc_channel_args, 1);
 
   grpc_ruby_once_init();
   rb_thread_call_without_gvl(
-      wait_until_channel_polling_thread_started_no_gil, NULL,
-      wait_until_channel_polling_thread_started_unblocking_func, NULL);
+      wait_until_channel_polling_thread_started_no_gil,
+      &stop_waiting_for_thread_start,
+      wait_until_channel_polling_thread_started_unblocking_func,
+      &stop_waiting_for_thread_start);
 
   /* "3" == 3 mandatory args */
   rb_scan_args(argc, argv, "3", &target, &channel_args, &credentials);
 
   TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
-  wrapper->mu_init_done = 0;
   target_chars = StringValueCStr(target);
   grpc_rb_hash_convert_to_channel_args(channel_args, &args);
   if (TYPE(credentials) == T_SYMBOL) {
@@ -196,24 +258,11 @@
   }
 
   GPR_ASSERT(ch);
-
-  wrapper->wrapped = ch;
-
-  gpr_mu_init(&wrapper->channel_mu);
-  gpr_cv_init(&wrapper->channel_cv);
-  wrapper->mu_init_done = 1;
-
-  gpr_mu_lock(&wrapper->channel_mu);
-  wrapper->abort_watch_connectivity_state = 0;
-  wrapper->current_connectivity_state =
-      grpc_channel_check_connectivity_state(wrapper->wrapped, 0);
-  wrapper->safe_to_destroy = 0;
-  wrapper->request_safe_destroy = 0;
-
-  gpr_cv_broadcast(&wrapper->channel_cv);
-  gpr_mu_unlock(&wrapper->channel_mu);
-
-  grpc_rb_channel_try_register_connection_polling(wrapper);
+  stack.channel = ch;
+  stack.wrapper = wrapper;
+  rb_thread_call_without_gvl(
+      channel_init_try_register_connection_polling_without_gil, &stack, NULL,
+      NULL);
 
   if (args.args != NULL) {
     xfree(args.args); /* Allocated by grpc_rb_hash_convert_to_channel_args */
@@ -224,10 +273,31 @@
     return Qnil;
   }
   rb_ivar_set(self, id_target, target);
-  wrapper->wrapped = ch;
   return self;
 }
 
+typedef struct get_state_stack {
+  bg_watched_channel *bg;
+  int try_to_connect;
+  int out;
+} get_state_stack;
+
+static void *get_state_without_gil(void *arg) {
+  get_state_stack *stack = (get_state_stack *)arg;
+
+  gpr_mu_lock(&global_connection_polling_mu);
+  GPR_ASSERT(abort_channel_polling || channel_polling_thread_started);
+  if (stack->bg->channel_destroyed) {
+    stack->out = GRPC_CHANNEL_SHUTDOWN;
+  } else {
+    stack->out = grpc_channel_check_connectivity_state(stack->bg->channel,
+                                                       stack->try_to_connect);
+  }
+  gpr_mu_unlock(&global_connection_polling_mu);
+
+  return NULL;
+}
+
 /*
   call-seq:
     ch.connectivity_state       -> state
@@ -240,59 +310,69 @@
 static VALUE grpc_rb_channel_get_connectivity_state(int argc, VALUE *argv,
                                                     VALUE self) {
   VALUE try_to_connect_param = Qfalse;
-  int grpc_try_to_connect = 0;
   grpc_rb_channel *wrapper = NULL;
-  grpc_channel *ch = NULL;
+  get_state_stack stack;
 
   /* "01" == 0 mandatory args, 1 (try_to_connect) is optional */
   rb_scan_args(argc, argv, "01", &try_to_connect_param);
-  grpc_try_to_connect = RTEST(try_to_connect_param) ? 1 : 0;
 
   TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
-  ch = wrapper->wrapped;
-  if (ch == NULL) {
+  if (wrapper->bg_wrapped == NULL) {
     rb_raise(rb_eRuntimeError, "closed!");
     return Qnil;
   }
-  return LONG2NUM(grpc_channel_check_connectivity_state(wrapper->wrapped,
-                                                        grpc_try_to_connect));
+
+  stack.bg = wrapper->bg_wrapped;
+  stack.try_to_connect = RTEST(try_to_connect_param) ? 1 : 0;
+  rb_thread_call_without_gvl(get_state_without_gil, &stack, NULL, NULL);
+
+  return LONG2NUM(stack.out);
 }
 
 typedef struct watch_state_stack {
-  grpc_rb_channel *wrapper;
+  grpc_channel *channel;
   gpr_timespec deadline;
   int last_state;
 } watch_state_stack;
 
-static void *watch_channel_state_without_gvl(void *arg) {
+static void *wait_for_watch_state_op_complete_without_gvl(void *arg) {
   watch_state_stack *stack = (watch_state_stack *)arg;
-  gpr_timespec deadline = stack->deadline;
-  grpc_rb_channel *wrapper = stack->wrapper;
-  int last_state = stack->last_state;
-  void *return_value = (void *)0;
+  watch_state_op *op = NULL;
+  void *success = (void *)0;
 
-  gpr_mu_lock(&wrapper->channel_mu);
-  while (wrapper->current_connectivity_state == last_state &&
-         !wrapper->request_safe_destroy && !wrapper->safe_to_destroy &&
-         !wrapper->abort_watch_connectivity_state &&
-         gpr_time_cmp(deadline, gpr_now(GPR_CLOCK_REALTIME)) > 0) {
-    gpr_cv_wait(&wrapper->channel_cv, &wrapper->channel_mu, deadline);
+  gpr_mu_lock(&global_connection_polling_mu);
+  // its unsafe to do a "watch" after "channel polling abort" because the cq has
+  // been shut down.
+  if (abort_channel_polling) {
+    gpr_mu_unlock(&global_connection_polling_mu);
+    return (void *)0;
   }
-  if (wrapper->current_connectivity_state != last_state) {
-    return_value = (void *)1;
-  }
-  gpr_mu_unlock(&wrapper->channel_mu);
+  op = gpr_zalloc(sizeof(watch_state_op));
+  op->op_type = WATCH_STATE_API;
+  grpc_channel_watch_connectivity_state(stack->channel, stack->last_state,
+                                        stack->deadline, channel_polling_cq,
+                                        op);
 
-  return return_value;
+  while (!op->op.api_callback_args.called_back) {
+    gpr_cv_wait(&global_connection_polling_cv, &global_connection_polling_mu,
+                gpr_inf_future(GPR_CLOCK_REALTIME));
+  }
+  if (op->op.api_callback_args.success) {
+    success = (void *)1;
+  }
+  gpr_free(op);
+  gpr_mu_unlock(&global_connection_polling_mu);
+
+  return success;
 }
-
-static void watch_channel_state_unblocking_func(void *arg) {
-  grpc_rb_channel *wrapper = (grpc_rb_channel *)arg;
-  gpr_log(GPR_DEBUG, "GRPC_RUBY: watch channel state unblocking func called");
-  gpr_mu_lock(&wrapper->channel_mu);
-  wrapper->abort_watch_connectivity_state = 1;
-  gpr_cv_broadcast(&wrapper->channel_cv);
-  gpr_mu_unlock(&wrapper->channel_mu);
+static void wait_for_watch_state_op_complete_unblocking_func(void *arg) {
+  bg_watched_channel *bg = (bg_watched_channel *)arg;
+  gpr_mu_lock(&global_connection_polling_mu);
+  if (!bg->channel_destroyed) {
+    grpc_channel_destroy(bg->channel);
+    bg->channel_destroyed = 1;
+  }
+  gpr_mu_unlock(&global_connection_polling_mu);
 }
 
 /* Wait until the channel's connectivity state becomes different from
@@ -307,11 +387,11 @@
                                                       VALUE deadline) {
   grpc_rb_channel *wrapper = NULL;
   watch_state_stack stack;
-  void *out;
+  void *op_success = 0;
 
   TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
 
-  if (wrapper->wrapped == NULL) {
+  if (wrapper->bg_wrapped == NULL) {
     rb_raise(rb_eRuntimeError, "closed!");
     return Qnil;
   }
@@ -323,16 +403,15 @@
     return Qnil;
   }
 
-  stack.wrapper = wrapper;
-  stack.deadline = grpc_rb_time_timeval(deadline, 0);
+  stack.channel = wrapper->bg_wrapped->channel;
+  stack.deadline = grpc_rb_time_timeval(deadline, 0),
   stack.last_state = NUM2LONG(last_state);
-  out =
-      rb_thread_call_without_gvl(watch_channel_state_without_gvl, &stack,
-                                 watch_channel_state_unblocking_func, wrapper);
-  if (out) {
-    return Qtrue;
-  }
-  return Qfalse;
+
+  op_success = rb_thread_call_without_gvl(
+      wait_for_watch_state_op_complete_without_gvl, &stack,
+      wait_for_watch_state_op_complete_unblocking_func, wrapper->bg_wrapped);
+
+  return op_success ? Qtrue : Qfalse;
 }
 
 /* Create a call given a grpc_channel, in order to call method. The request
@@ -344,7 +423,6 @@
   grpc_rb_channel *wrapper = NULL;
   grpc_call *call = NULL;
   grpc_call *parent_call = NULL;
-  grpc_channel *ch = NULL;
   grpc_completion_queue *cq = NULL;
   int flags = GRPC_PROPAGATE_DEFAULTS;
   grpc_slice method_slice;
@@ -366,8 +444,7 @@
 
   cq = grpc_completion_queue_create_for_pluck(NULL);
   TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
-  ch = wrapper->wrapped;
-  if (ch == NULL) {
+  if (wrapper->bg_wrapped == NULL) {
     rb_raise(rb_eRuntimeError, "closed!");
     return Qnil;
   }
@@ -375,8 +452,8 @@
   method_slice =
       grpc_slice_from_copied_buffer(RSTRING_PTR(method), RSTRING_LEN(method));
 
-  call = grpc_channel_create_call(ch, parent_call, flags, cq, method_slice,
-                                  host_slice_ptr,
+  call = grpc_channel_create_call(wrapper->bg_wrapped->channel, parent_call,
+                                  flags, cq, method_slice, host_slice_ptr,
                                   grpc_rb_time_timeval(deadline,
                                                        /* absolute time */ 0),
                                   NULL);
@@ -401,15 +478,16 @@
 }
 
 /* Closes the channel, calling it's destroy method */
+/* Note this is an API-level call; a wrapped channel's finalizer doesn't call
+ * this */
 static VALUE grpc_rb_channel_destroy(VALUE self) {
   grpc_rb_channel *wrapper = NULL;
-  grpc_channel *ch = NULL;
 
   TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
-  ch = wrapper->wrapped;
-  if (ch != NULL) {
-    grpc_rb_channel_safe_destroy(wrapper);
-    wrapper->wrapped = NULL;
+  if (wrapper->bg_wrapped != NULL) {
+    rb_thread_call_without_gvl(channel_safe_destroy_without_gil,
+                               wrapper->bg_wrapped, NULL, NULL);
+    wrapper->bg_wrapped = NULL;
   }
 
   return Qnil;
@@ -422,64 +500,110 @@
   char *target = NULL;
 
   TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
-  target = grpc_channel_get_target(wrapper->wrapped);
+  target = grpc_channel_get_target(wrapper->bg_wrapped->channel);
   res = rb_str_new2(target);
   gpr_free(target);
 
   return res;
 }
 
-// Either start polling channel connection state or signal that it's free to
-// destroy.
-// Not safe to call while a channel's connection state is polled.
-static void grpc_rb_channel_try_register_connection_polling(
-    grpc_rb_channel *wrapper) {
-  grpc_connectivity_state conn_state;
-  gpr_timespec sleep_time = gpr_time_add(
-      gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_millis(20, GPR_TIMESPAN));
+/* Needs to be called under global_connection_polling_mu */
+static int bg_watched_channel_list_lookup(bg_watched_channel *target) {
+  bg_watched_channel *cur = bg_watched_channel_list_head;
 
-  GPR_ASSERT(wrapper);
-  GPR_ASSERT(wrapper->wrapped);
-  gpr_mu_lock(&wrapper->channel_mu);
-  if (wrapper->request_safe_destroy) {
-    wrapper->safe_to_destroy = 1;
-    gpr_cv_broadcast(&wrapper->channel_cv);
-    gpr_mu_unlock(&wrapper->channel_mu);
-    return;
+  while (cur != NULL) {
+    if (cur == target) {
+      return 1;
+    }
+    cur = cur->next;
   }
-  gpr_mu_lock(&global_connection_polling_mu);
 
-  GPR_ASSERT(channel_polling_thread_started || abort_channel_polling);
-  conn_state = grpc_channel_check_connectivity_state(wrapper->wrapped, 0);
-  if (conn_state != wrapper->current_connectivity_state) {
-    wrapper->current_connectivity_state = conn_state;
-    gpr_cv_broadcast(&wrapper->channel_cv);
-  }
-  // avoid posting work to the channel polling cq if it's been shutdown
-  if (!abort_channel_polling && conn_state != GRPC_CHANNEL_SHUTDOWN) {
-    grpc_channel_watch_connectivity_state(
-        wrapper->wrapped, conn_state, sleep_time, channel_polling_cq, wrapper);
-  } else {
-    wrapper->safe_to_destroy = 1;
-    gpr_cv_broadcast(&wrapper->channel_cv);
-  }
-  gpr_mu_unlock(&global_connection_polling_mu);
-  gpr_mu_unlock(&wrapper->channel_mu);
+  return 0;
 }
 
-// Note requires wrapper->wrapped, wrapper->channel_mu/cv initialized
-static void grpc_rb_channel_safe_destroy(grpc_rb_channel *wrapper) {
-  gpr_mu_lock(&wrapper->channel_mu);
-  wrapper->request_safe_destroy = 1;
+/* Needs to be called under global_connection_polling_mu */
+static bg_watched_channel *bg_watched_channel_list_create_and_add(
+    grpc_channel *channel) {
+  bg_watched_channel *watched = gpr_zalloc(sizeof(bg_watched_channel));
 
-  while (!wrapper->safe_to_destroy) {
-    gpr_cv_wait(&wrapper->channel_cv, &wrapper->channel_mu,
-                gpr_inf_future(GPR_CLOCK_REALTIME));
+  watched->channel = channel;
+  watched->next = bg_watched_channel_list_head;
+  watched->refcount = 1;
+  bg_watched_channel_list_head = watched;
+  return watched;
+}
+
+/* Needs to be called under global_connection_polling_mu */
+static void bg_watched_channel_list_free_and_remove(
+    bg_watched_channel *target) {
+  bg_watched_channel *bg = NULL;
+
+  GPR_ASSERT(bg_watched_channel_list_lookup(target));
+  GPR_ASSERT(target->channel_destroyed && target->refcount == 0);
+  if (bg_watched_channel_list_head == target) {
+    bg_watched_channel_list_head = target->next;
+    gpr_free(target);
+    return;
   }
-  GPR_ASSERT(wrapper->safe_to_destroy);
-  gpr_mu_unlock(&wrapper->channel_mu);
+  bg = bg_watched_channel_list_head;
+  while (bg != NULL && bg->next != NULL) {
+    if (bg->next == target) {
+      bg->next = bg->next->next;
+      gpr_free(target);
+      return;
+    }
+    bg = bg->next;
+  }
+  GPR_ASSERT(0);
+}
 
-  grpc_channel_destroy(wrapper->wrapped);
+/* Initialize a grpc_rb_channel's "protected grpc_channel" and try to push
+ * it onto the background thread for constant watches. */
+static void *channel_init_try_register_connection_polling_without_gil(
+    void *arg) {
+  channel_init_try_register_stack *stack =
+      (channel_init_try_register_stack *)arg;
+
+  gpr_mu_lock(&global_connection_polling_mu);
+  stack->wrapper->bg_wrapped =
+      bg_watched_channel_list_create_and_add(stack->channel);
+  grpc_rb_channel_try_register_connection_polling(stack->wrapper->bg_wrapped);
+  gpr_mu_unlock(&global_connection_polling_mu);
+  return NULL;
+}
+
+// Needs to be called under global_connection_poolling_mu
+static void grpc_rb_channel_try_register_connection_polling(
+    bg_watched_channel *bg) {
+  grpc_connectivity_state conn_state;
+  watch_state_op *op = NULL;
+
+  GPR_ASSERT(channel_polling_thread_started || abort_channel_polling);
+
+  if (bg->refcount == 0) {
+    GPR_ASSERT(bg->channel_destroyed);
+    bg_watched_channel_list_free_and_remove(bg);
+    return;
+  }
+  GPR_ASSERT(bg->refcount == 1);
+  if (bg->channel_destroyed || abort_channel_polling) {
+    return;
+  }
+
+  conn_state = grpc_channel_check_connectivity_state(bg->channel, 0);
+  if (conn_state == GRPC_CHANNEL_SHUTDOWN) {
+    return;
+  }
+  GPR_ASSERT(bg_watched_channel_list_lookup(bg));
+  // prevent bg from being free'd by GC while background thread is watching it
+  bg->refcount++;
+
+  op = gpr_zalloc(sizeof(watch_state_op));
+  op->op_type = CONTINUOUS_WATCH;
+  op->op.continuous_watch_callback_args.bg = bg;
+  grpc_channel_watch_connectivity_state(bg->channel, conn_state,
+                                        gpr_inf_future(GPR_CLOCK_REALTIME),
+                                        channel_polling_cq, op);
 }
 
 // Note this loop breaks out with a single call of
@@ -490,6 +614,8 @@
 // early and falls back to current behavior.
 static void *run_poll_channels_loop_no_gil(void *arg) {
   grpc_event event;
+  watch_state_op *op = NULL;
+  bg_watched_channel *bg = NULL;
   (void)arg;
   gpr_log(GPR_DEBUG, "GRPC_RUBY: run_poll_channels_loop_no_gil - begin");
 
@@ -505,10 +631,22 @@
     if (event.type == GRPC_QUEUE_SHUTDOWN) {
       break;
     }
+    gpr_mu_lock(&global_connection_polling_mu);
     if (event.type == GRPC_OP_COMPLETE) {
-      grpc_rb_channel_try_register_connection_polling(
-          (grpc_rb_channel *)event.tag);
+      op = (watch_state_op *)event.tag;
+      if (op->op_type == CONTINUOUS_WATCH) {
+        bg = (bg_watched_channel *)op->op.continuous_watch_callback_args.bg;
+        bg->refcount--;
+        grpc_rb_channel_try_register_connection_polling(bg);
+        gpr_free(op);
+      } else if (op->op_type == WATCH_STATE_API) {
+        grpc_rb_channel_watch_connection_state_op_complete(
+            (watch_state_op *)event.tag, event.success);
+      } else {
+        GPR_ASSERT(0);
+      }
     }
+    gpr_mu_unlock(&global_connection_polling_mu);
   }
   grpc_completion_queue_destroy(channel_polling_cq);
   gpr_log(GPR_DEBUG,
@@ -519,14 +657,36 @@
 
 // Notify the channel polling loop to cleanup and shutdown.
 static void run_poll_channels_loop_unblocking_func(void *arg) {
+  bg_watched_channel *bg = NULL;
   (void)arg;
+
   gpr_mu_lock(&global_connection_polling_mu);
   gpr_log(GPR_DEBUG,
-          "GRPC_RUBY: grpc_rb_event_unblocking_func - begin aborting "
+          "GRPC_RUBY: run_poll_channels_loop_unblocking_func - begin aborting "
           "connection polling");
+  // early out after first time through
+  if (abort_channel_polling) {
+    gpr_mu_unlock(&global_connection_polling_mu);
+    return;
+  }
   abort_channel_polling = 1;
+
+  // force pending watches to end by switching to shutdown state
+  bg = bg_watched_channel_list_head;
+  while (bg != NULL) {
+    if (!bg->channel_destroyed) {
+      grpc_channel_destroy(bg->channel);
+      bg->channel_destroyed = 1;
+    }
+    bg = bg->next;
+  }
+
   grpc_completion_queue_shutdown(channel_polling_cq);
+  gpr_cv_broadcast(&global_connection_polling_cv);
   gpr_mu_unlock(&global_connection_polling_mu);
+  gpr_log(GPR_DEBUG,
+          "GRPC_RUBY: run_poll_channels_loop_unblocking_func - end aborting "
+          "connection polling");
 }
 
 // Poll channel connectivity states in background thread without the GIL.
@@ -542,10 +702,11 @@
 }
 
 static void *wait_until_channel_polling_thread_started_no_gil(void *arg) {
-  (void)arg;
+  int *stop_waiting = (int *)arg;
   gpr_log(GPR_DEBUG, "GRPC_RUBY: wait for channel polling thread to start");
   gpr_mu_lock(&global_connection_polling_mu);
-  while (!channel_polling_thread_started && !abort_channel_polling) {
+  while (!channel_polling_thread_started && !abort_channel_polling &&
+         !*stop_waiting) {
     gpr_cv_wait(&global_connection_polling_cv, &global_connection_polling_mu,
                 gpr_inf_future(GPR_CLOCK_REALTIME));
   }
@@ -556,15 +717,22 @@
 
 static void wait_until_channel_polling_thread_started_unblocking_func(
     void *arg) {
-  (void)arg;
+  int *stop_waiting = (int *)arg;
   gpr_mu_lock(&global_connection_polling_mu);
   gpr_log(GPR_DEBUG,
-          "GRPC_RUBY: "
-          "wait_until_channel_polling_thread_started_unblocking_func - begin "
-          "aborting connection polling");
+          "GRPC_RUBY: interrupt wait for channel polling thread to start");
+  *stop_waiting = 1;
+  gpr_cv_broadcast(&global_connection_polling_cv);
+  gpr_mu_unlock(&global_connection_polling_mu);
+}
+
+static void *set_abort_channel_polling_without_gil(void *arg) {
+  (void)arg;
+  gpr_mu_lock(&global_connection_polling_mu);
   abort_channel_polling = 1;
   gpr_cv_broadcast(&global_connection_polling_cv);
   gpr_mu_unlock(&global_connection_polling_mu);
+  return NULL;
 }
 
 /* Temporary fix for
@@ -592,10 +760,8 @@
 
   if (!RTEST(background_thread)) {
     gpr_log(GPR_DEBUG, "GRPC_RUBY: failed to spawn channel polling thread");
-    gpr_mu_lock(&global_connection_polling_mu);
-    abort_channel_polling = 1;
-    gpr_cv_broadcast(&global_connection_polling_cv);
-    gpr_mu_unlock(&global_connection_polling_mu);
+    rb_thread_call_without_gvl(set_abort_channel_polling_without_gil, NULL,
+                               NULL, NULL);
   }
 }
 
@@ -674,5 +840,5 @@
 grpc_channel *grpc_rb_get_wrapped_channel(VALUE v) {
   grpc_rb_channel *wrapper = NULL;
   TypedData_Get_Struct(v, grpc_rb_channel, &grpc_channel_data_type, wrapper);
-  return wrapper->wrapped;
+  return wrapper->bg_wrapped->channel;
 }
diff --git a/src/ruby/ext/grpc/rb_event_thread.c b/src/ruby/ext/grpc/rb_event_thread.c
index 9a3b56d..7113826 100644
--- a/src/ruby/ext/grpc/rb_event_thread.c
+++ b/src/ruby/ext/grpc/rb_event_thread.c
@@ -105,16 +105,16 @@
   grpc_rb_event *event = NULL;
   (void)param;
   gpr_mu_lock(&event_queue.mu);
-  while ((event = grpc_rb_event_queue_dequeue()) == NULL) {
+  while (!event_queue.abort) {
+    if ((event = grpc_rb_event_queue_dequeue()) != NULL) {
+      gpr_mu_unlock(&event_queue.mu);
+      return event;
+    }
     gpr_cv_wait(&event_queue.cv, &event_queue.mu,
                 gpr_inf_future(GPR_CLOCK_REALTIME));
-    if (event_queue.abort) {
-      gpr_mu_unlock(&event_queue.mu);
-      return NULL;
-    }
   }
   gpr_mu_unlock(&event_queue.mu);
-  return event;
+  return NULL;
 }
 
 static void grpc_rb_event_unblocking_func(void *arg) {
diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c
index 5be8861..c319cd1 100644
--- a/src/ruby/ext/grpc/rb_grpc.c
+++ b/src/ruby/ext/grpc/rb_grpc.c
@@ -292,11 +292,12 @@
 
 static void grpc_ruby_once_init_internal() {
   grpc_init();
-  grpc_rb_event_queue_thread_start();
-  grpc_rb_channel_polling_thread_start();
   atexit(grpc_rb_shutdown);
 }
 
+static VALUE bg_thread_init_rb_mu = Qundef;
+static int bg_thread_init_done = 0;
+
 void grpc_ruby_once_init() {
   /* ruby_vm_at_exit doesn't seem to be working. It would crash once every
    * blue moon, and some users are getting it repeatedly. See the discussions
@@ -309,6 +310,18 @@
    * schedule our initialization and destruction only once.
    */
   gpr_once_init(&g_once_init, grpc_ruby_once_init_internal);
+
+  // Avoid calling calling into ruby library (when creating threads here)
+  // in gpr_once_init. In general, it appears to be unsafe to call
+  // into the ruby library while holding a non-ruby mutex, because a gil yield
+  // could end up trying to lock onto that same mutex and deadlocking.
+  rb_mutex_lock(bg_thread_init_rb_mu);
+  if (!bg_thread_init_done) {
+    grpc_rb_event_queue_thread_start();
+    grpc_rb_channel_polling_thread_start();
+    bg_thread_init_done = 1;
+  }
+  rb_mutex_unlock(bg_thread_init_rb_mu);
 }
 
 void Init_grpc_c() {
@@ -317,6 +330,9 @@
     return;
   }
 
+  bg_thread_init_rb_mu = rb_mutex_new();
+  rb_global_variable(&bg_thread_init_rb_mu);
+
   grpc_rb_mGRPC = rb_define_module("GRPC");
   grpc_rb_mGrpcCore = rb_define_module_under(grpc_rb_mGRPC, "Core");
   grpc_rb_sNewServerRpc = rb_struct_define(
diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.c b/src/ruby/ext/grpc/rb_grpc_imports.generated.c
index 221a1e1..f6a4ff6 100644
--- a/src/ruby/ext/grpc/rb_grpc_imports.generated.c
+++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.c
@@ -103,6 +103,7 @@
 grpc_alarm_cancel_type grpc_alarm_cancel_import;
 grpc_alarm_destroy_type grpc_alarm_destroy_import;
 grpc_channel_check_connectivity_state_type grpc_channel_check_connectivity_state_import;
+grpc_channel_num_external_connectivity_watchers_type grpc_channel_num_external_connectivity_watchers_import;
 grpc_channel_watch_connectivity_state_type grpc_channel_watch_connectivity_state_import;
 grpc_channel_create_call_type grpc_channel_create_call_import;
 grpc_channel_ping_type grpc_channel_ping_import;
@@ -270,6 +271,7 @@
 gpr_histogram_merge_contents_type gpr_histogram_merge_contents_import;
 gpr_join_host_port_type gpr_join_host_port_import;
 gpr_split_host_port_type gpr_split_host_port_import;
+gpr_log_severity_string_type gpr_log_severity_string_import;
 gpr_log_type gpr_log_import;
 gpr_log_message_type gpr_log_message_import;
 gpr_set_log_verbosity_type gpr_set_log_verbosity_import;
@@ -404,6 +406,7 @@
   grpc_alarm_cancel_import = (grpc_alarm_cancel_type) GetProcAddress(library, "grpc_alarm_cancel");
   grpc_alarm_destroy_import = (grpc_alarm_destroy_type) GetProcAddress(library, "grpc_alarm_destroy");
   grpc_channel_check_connectivity_state_import = (grpc_channel_check_connectivity_state_type) GetProcAddress(library, "grpc_channel_check_connectivity_state");
+  grpc_channel_num_external_connectivity_watchers_import = (grpc_channel_num_external_connectivity_watchers_type) GetProcAddress(library, "grpc_channel_num_external_connectivity_watchers");
   grpc_channel_watch_connectivity_state_import = (grpc_channel_watch_connectivity_state_type) GetProcAddress(library, "grpc_channel_watch_connectivity_state");
   grpc_channel_create_call_import = (grpc_channel_create_call_type) GetProcAddress(library, "grpc_channel_create_call");
   grpc_channel_ping_import = (grpc_channel_ping_type) GetProcAddress(library, "grpc_channel_ping");
@@ -571,6 +574,7 @@
   gpr_histogram_merge_contents_import = (gpr_histogram_merge_contents_type) GetProcAddress(library, "gpr_histogram_merge_contents");
   gpr_join_host_port_import = (gpr_join_host_port_type) GetProcAddress(library, "gpr_join_host_port");
   gpr_split_host_port_import = (gpr_split_host_port_type) GetProcAddress(library, "gpr_split_host_port");
+  gpr_log_severity_string_import = (gpr_log_severity_string_type) GetProcAddress(library, "gpr_log_severity_string");
   gpr_log_import = (gpr_log_type) GetProcAddress(library, "gpr_log");
   gpr_log_message_import = (gpr_log_message_type) GetProcAddress(library, "gpr_log_message");
   gpr_set_log_verbosity_import = (gpr_set_log_verbosity_type) GetProcAddress(library, "gpr_set_log_verbosity");
diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
index f62b31e..0d64290 100644
--- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h
+++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
@@ -260,6 +260,9 @@
 typedef grpc_connectivity_state(*grpc_channel_check_connectivity_state_type)(grpc_channel *channel, int try_to_connect);
 extern grpc_channel_check_connectivity_state_type grpc_channel_check_connectivity_state_import;
 #define grpc_channel_check_connectivity_state grpc_channel_check_connectivity_state_import
+typedef int(*grpc_channel_num_external_connectivity_watchers_type)(grpc_channel *channel);
+extern grpc_channel_num_external_connectivity_watchers_type grpc_channel_num_external_connectivity_watchers_import;
+#define grpc_channel_num_external_connectivity_watchers grpc_channel_num_external_connectivity_watchers_import
 typedef void(*grpc_channel_watch_connectivity_state_type)(grpc_channel *channel, grpc_connectivity_state last_observed_state, gpr_timespec deadline, grpc_completion_queue *cq, void *tag);
 extern grpc_channel_watch_connectivity_state_type grpc_channel_watch_connectivity_state_import;
 #define grpc_channel_watch_connectivity_state grpc_channel_watch_connectivity_state_import
@@ -761,6 +764,9 @@
 typedef int(*gpr_split_host_port_type)(const char *name, char **host, char **port);
 extern gpr_split_host_port_type gpr_split_host_port_import;
 #define gpr_split_host_port gpr_split_host_port_import
+typedef const char *(*gpr_log_severity_string_type)(gpr_log_severity severity);
+extern gpr_log_severity_string_type gpr_log_severity_string_import;
+#define gpr_log_severity_string gpr_log_severity_string_import
 typedef void(*gpr_log_type)(const char *file, int line, gpr_log_severity severity, const char *format, ...) GPR_PRINT_FORMAT_CHECK(4, 5);
 extern gpr_log_type gpr_log_import;
 #define gpr_log gpr_log_import
diff --git a/src/ruby/lib/grpc/version.rb b/src/ruby/lib/grpc/version.rb
index f30dff3..103e5cb 100644
--- a/src/ruby/lib/grpc/version.rb
+++ b/src/ruby/lib/grpc/version.rb
@@ -29,5 +29,5 @@
 
 # GRPC contains the General RPC module.
 module GRPC
-  VERSION = '1.4.0.dev'
+  VERSION = '1.4.0.pre1'
 end
diff --git a/src/ruby/spec/channel_connection_spec.rb b/src/ruby/spec/channel_connection_spec.rb
index 940d68b..c8a7856 100644
--- a/src/ruby/spec/channel_connection_spec.rb
+++ b/src/ruby/spec/channel_connection_spec.rb
@@ -28,6 +28,10 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 require 'grpc'
+require 'timeout'
+
+include Timeout
+include GRPC::Core
 
 # A test message
 class EchoMsg
@@ -62,7 +66,7 @@
 EchoStub = EchoService.rpc_stub_class
 
 def start_server(port = 0)
-  @srv = GRPC::RpcServer.new
+  @srv = GRPC::RpcServer.new(pool_size: 1)
   server_port = @srv.add_http2_port("localhost:#{port}", :this_port_is_insecure)
   @srv.handle(EchoService)
   @server_thd = Thread.new { @srv.run }
@@ -138,4 +142,32 @@
 
     stop_server
   end
+
+  it 'concurrent watches on the same channel' do
+    timeout(180) do
+      port = start_server
+      ch = GRPC::Core::Channel.new("localhost:#{port}", {},
+                                   :this_channel_is_insecure)
+      stop_server
+
+      thds = []
+      50.times do
+        thds << Thread.new do
+          while ch.connectivity_state(true) != ConnectivityStates::READY
+            ch.watch_connectivity_state(
+              ConnectivityStates::READY, Time.now + 60)
+            break
+          end
+        end
+      end
+
+      sleep 0.01
+
+      start_server(port)
+
+      thds.each(&:join)
+
+      stop_server
+    end
+  end
 end
diff --git a/src/ruby/tools/version.rb b/src/ruby/tools/version.rb
index 1f8d4af..9ce13f3 100644
--- a/src/ruby/tools/version.rb
+++ b/src/ruby/tools/version.rb
@@ -29,6 +29,6 @@
 
 module GRPC
   module Tools
-    VERSION = '1.4.0.dev'
+    VERSION = '1.4.0.pre1'
   end
 end
diff --git a/templates/README.md b/templates/README.md
index eedc6e9..7b77072 100644
--- a/templates/README.md
+++ b/templates/README.md
@@ -1,3 +1,12 @@
+# Regenerating project files
+
+Prerequisites: `python`, `pip install mako`
+
+```
+# Regenerate the projects files using templates
+tools/buildgen/generate_projects.sh
+```
+
 # Quick justification
 
 We've approached the problem of the build system from a lot of different
diff --git a/templates/config.m4.template b/templates/config.m4.template
index a6357b7..f91893c 100644
--- a/templates/config.m4.template
+++ b/templates/config.m4.template
@@ -35,7 +35,7 @@
       ${source} ${"\\"}
       % endfor
       % for lib in libs:
-      % if lib.name in php_config_m4.get('deps', []):
+      % if lib.name in php_config_m4.get('deps', []) and lib.name != 'z':
       % for source in lib.src:
       ${source} ${"\\"}
       % endfor
@@ -49,7 +49,7 @@
   <%
     dirs = {}
     for lib in libs:
-      if lib.name in php_config_m4.get('deps', []):
+      if lib.name in php_config_m4.get('deps', []) and lib.name != 'z':
         for source in lib.src:
           dirs[source[:source.rfind('/')]] = 1
     dirs = dirs.keys()
diff --git a/templates/config.w32.template b/templates/config.w32.template
new file mode 100644
index 0000000..4edef96
--- /dev/null
+++ b/templates/config.w32.template
@@ -0,0 +1,67 @@
+%YAML 1.2
+--- |
+  // $Id$
+  // vim:ft=javascript
+
+  ARG_WITH("grpc", "grpc support", "no");
+
+  if (PHP_GRPC != "no") {
+
+    grpc_source =
+      % for source in php_config_m4.src:
+      "${source.replace('/','\\\\')} " +
+      % endfor
+      % for lib in libs:
+      % if lib.name in php_config_m4.get('deps', []) and lib.name != 'ares':
+      % for source in lib.src:
+      "${source.replace('/','\\\\')} " +
+      % endfor
+      % endif
+      % endfor
+      "";
+
+    EXTENSION("grpc", grpc_source, null,
+      "/DOPENSSL_NO_ASM /D_GNU_SOURCE /DWIN32_LEAN_AND_MEAN "+
+      "/D_HAS_EXCEPTIONS=0 /DNOMINMAX /DGRPC_ARES=0 /D_WIN32_WINNT=0x600 "+
+      "/I"+configure_module_dirname+" "+
+      "/I"+configure_module_dirname+"\\include "+
+      "/I"+configure_module_dirname+"\\src\\php\\ext\\grpc "+
+      "/I"+configure_module_dirname+"\\third_party\\boringssl\\include "+
+      "/I"+configure_module_dirname+"\\third_party\\zlib");
+  <%
+    dirs = {}
+    for lib in libs:
+      if lib.name in php_config_m4.get('deps', []) and lib.name != 'ares':
+        for source in lib.src:
+          tmp = source
+          prev = ''
+          while (True):
+            idx = tmp.find('/');
+            if (idx == -1):
+              break
+            dirs[prev + '\\\\' + tmp[:idx]] = 1
+            prev += ('\\\\' + tmp[:idx]);
+            tmp = tmp[idx+1:]
+            
+    dirs['\\\\src'] = 1;
+    dirs['\\\\src\\\\php'] = 1;
+    dirs['\\\\src\\\\php\\\\ext'] = 1;
+    dirs['\\\\src\\\\php\\\\ext\\\\grpc'] = 1;
+    dirs = dirs.keys()
+    dirs.sort()
+  %>
+    base_dir = get_define('BUILD_DIR');
+    FSO.CreateFolder(base_dir+"\\ext");
+    FSO.CreateFolder(base_dir+"\\ext\\grpc");
+    % for dir in dirs:
+    FSO.CreateFolder(base_dir+"\\ext\\grpc${dir}");
+    % endfor
+    _build_dirs = new Array();
+    for (i = 0; i < build_dirs.length; i++) {
+      if (build_dirs[i].indexOf('grpc') == -1) {
+        _build_dirs[_build_dirs.length] = build_dirs[i];
+      }
+    }
+    build_dirs = _build_dirs;
+
+  }
diff --git a/templates/package.json.template b/templates/package.json.template
index 551c25f..1027e7b 100644
--- a/templates/package.json.template
+++ b/templates/package.json.template
@@ -35,7 +35,7 @@
       "arguejs": "^0.2.3",
       "lodash": "^4.15.0",
       "nan": "^2.0.0",
-      "node-pre-gyp": "^0.6.0",
+      "node-pre-gyp": "^0.6.35",
       "protobufjs": "^5.0.0"
     },
     "devDependencies": {
diff --git a/templates/package.xml.template b/templates/package.xml.template
index 394b815..6a43ff4 100644
--- a/templates/package.xml.template
+++ b/templates/package.xml.template
@@ -12,7 +12,7 @@
     <email>grpc-packages@google.com</email>
     <active>yes</active>
    </lead>
-   <date>2017-05-05</date>
+   <date>2017-05-22</date>
    <time>16:06:07</time>
    <version>
     <release>${settings.php_version.php()}</release>
@@ -25,13 +25,18 @@
    <license>BSD</license>
    <notes>
   - Fixed some memory leaks #9559, #10996
+  - Disabled cares dependency from gRPC C Core #10940
+  - De-coupled protobuf dependency #11112
+  - Fixed extension reported version #10842
+  - Added config.w32 for Windows support #8161
+  - Fixed PHP distrib test after cc files were added #11193
+  - Fixed protoc plugin comment escape bug #11025
    </notes>
    <contents>
     <dir baseinstalldir="/" name="/">
       <file baseinstalldir="/" name="config.m4" role="src" />
+      <file baseinstalldir="/" name="config.w32" role="src" />
       <file baseinstalldir="/" name="src/php/README.md" role="src" />
-      <file baseinstalldir="/" name="src/php/ext/grpc/CREDITS" role="src" />
-      <file baseinstalldir="/" name="src/php/ext/grpc/LICENSE" role="src" />
       % for source in php_config_m4.src + php_config_m4.headers:
       <file baseinstalldir="/" name="${source}" role="src" />
       % endfor
diff --git a/templates/src/csharp/build_packages_dotnetcli.bat.template b/templates/src/csharp/build_packages_dotnetcli.bat.template
index 8624c34..3db1e0a 100755
--- a/templates/src/csharp/build_packages_dotnetcli.bat.template
+++ b/templates/src/csharp/build_packages_dotnetcli.bat.template
@@ -53,11 +53,11 @@
   @rem To be able to build, we also need to put grpc_csharp_ext to its normal location
   xcopy /Y /I nativelibs\csharp_ext_windows_x64\grpc_csharp_ext.dll ..\..\cmake\build\x64\Release${"\\"}
   
-  %%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Core --output ..\..\..\artifacts || goto :error
-  %%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Core.Testing --output ..\..\..\artifacts || goto :error
-  %%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Auth --output ..\..\..\artifacts || goto :error
-  %%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.HealthCheck --output ..\..\..\artifacts || goto :error
-  %%DOTNET% pack --configuration Release --include-symbols --include-source Grpc.Reflection --output ..\..\..\artifacts || goto :error
+  %%DOTNET% pack --configuration Release Grpc.Core --output ..\..\..\artifacts || goto :error
+  %%DOTNET% pack --configuration Release Grpc.Core.Testing --output ..\..\..\artifacts || goto :error
+  %%DOTNET% pack --configuration Release Grpc.Auth --output ..\..\..\artifacts || goto :error
+  %%DOTNET% pack --configuration Release Grpc.HealthCheck --output ..\..\..\artifacts || goto :error
+  %%DOTNET% pack --configuration Release Grpc.Reflection --output ..\..\..\artifacts || goto :error
   
   %%NUGET% pack Grpc.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts || goto :error
   %%NUGET% pack Grpc.Tools.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts
diff --git a/templates/src/csharp/build_packages_dotnetcli.sh.template b/templates/src/csharp/build_packages_dotnetcli.sh.template
index bff497c..65afec5 100755
--- a/templates/src/csharp/build_packages_dotnetcli.sh.template
+++ b/templates/src/csharp/build_packages_dotnetcli.sh.template
@@ -50,11 +50,11 @@
   mkdir -p ../../libs/opt
   cp nativelibs/csharp_ext_linux_x64/libgrpc_csharp_ext.so ../../libs/opt
   
-  dotnet pack --configuration Release --include-symbols --include-source Grpc.Core --output ../../../artifacts
-  dotnet pack --configuration Release --include-symbols --include-source Grpc.Core.Testing --output ../../../artifacts
-  dotnet pack --configuration Release --include-symbols --include-source Grpc.Auth --output ../../../artifacts
-  dotnet pack --configuration Release --include-symbols --include-source Grpc.HealthCheck --output ../../../artifacts
-  dotnet pack --configuration Release --include-symbols --include-source Grpc.Reflection --output ../../../artifacts
+  dotnet pack --configuration Release Grpc.Core --output ../../../artifacts
+  dotnet pack --configuration Release Grpc.Core.Testing --output ../../../artifacts
+  dotnet pack --configuration Release Grpc.Auth --output ../../../artifacts
+  dotnet pack --configuration Release Grpc.HealthCheck --output ../../../artifacts
+  dotnet pack --configuration Release Grpc.Reflection --output ../../../artifacts
   
   nuget pack Grpc.nuspec -Version "${settings.csharp_version}" -OutputDirectory ../../artifacts
   nuget pack Grpc.Tools.nuspec -Version "${settings.csharp_version}" -OutputDirectory ../../artifacts
diff --git a/templates/src/php/ext/grpc/version.h.template b/templates/src/php/ext/grpc/version.h.template
index 828ea69..10977a8 100644
--- a/templates/src/php/ext/grpc/version.h.template
+++ b/templates/src/php/ext/grpc/version.h.template
@@ -37,6 +37,6 @@
   #ifndef VERSION_H
   #define VERSION_H
 
-  #define PHP_GRPC_VERSION "${settings.php_version.php_composer()}"
+  #define PHP_GRPC_VERSION "${settings.php_version.php()}"
 
   #endif /* VERSION_H */
diff --git a/test/core/census/intrusive_hash_map_test.c b/test/core/census/intrusive_hash_map_test.c
new file mode 100644
index 0000000..fe8d3a1
--- /dev/null
+++ b/test/core/census/intrusive_hash_map_test.c
@@ -0,0 +1,299 @@
+/*
+ *
+ * Copyright 2017, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/ext/census/intrusive_hash_map.h"
+
+#include <grpc/support/log.h>
+#include <grpc/support/useful.h>
+#include "test/core/util/test_config.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* The initial size of an intrusive hash map will be 2 to this power. */
+static const uint32_t kInitialLog2Size = 4;
+
+/* Simple object used for testing intrusive_hash_map. */
+typedef struct object { uint64_t val; } object;
+
+/* Helper function to allocate and initialize object. */
+static inline object *make_new_object(uint64_t val) {
+  object *obj = (object *)gpr_malloc(sizeof(object));
+  obj->val = val;
+  return obj;
+}
+
+/* Wrapper struct for object. */
+typedef struct ptr_item {
+  INTRUSIVE_HASH_MAP_HEADER;
+  object *obj;
+} ptr_item;
+
+/* Helper function that creates a new hash map item.  It is up to the user to
+ * free the item that was allocated. */
+static inline ptr_item *make_ptr_item(uint64_t key, uint64_t value) {
+  ptr_item *new_item = (ptr_item *)gpr_malloc(sizeof(ptr_item));
+  new_item->IHM_key = key;
+  new_item->IHM_hash_link = NULL;
+  new_item->obj = make_new_object(value);
+  return new_item;
+}
+
+/* Helper function to deallocate ptr_item. */
+static void free_ptr_item(void *ptr) { gpr_free(((ptr_item *)ptr)->obj); }
+
+/* Simple string object used for testing intrusive_hash_map. */
+typedef struct string_item {
+  INTRUSIVE_HASH_MAP_HEADER;
+  // User data.
+  char buf[32];
+  uint16_t len;
+} string_item;
+
+/* Helper function to allocate and initialize string object. */
+static string_item *make_string_item(uint64_t key, const char *buf,
+                                     uint16_t len) {
+  string_item *item = (string_item *)gpr_malloc(sizeof(string_item));
+  item->IHM_key = key;
+  item->IHM_hash_link = NULL;
+  item->len = len;
+  memcpy(item->buf, buf, sizeof(char) * len);
+  return item;
+}
+
+/* Helper function for comparing two string objects. */
+static bool compare_string_item(const string_item *A, const string_item *B) {
+  if (A->IHM_key != B->IHM_key || A->len != B->len)
+    return false;
+  else {
+    for (int i = 0; i < A->len; ++i) {
+      if (A->buf[i] != B->buf[i]) return false;
+    }
+  }
+
+  return true;
+}
+
+void test_empty() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+  GPR_ASSERT(0 == intrusive_hash_map_size(&hash_map));
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, NULL);
+}
+
+void test_single_item() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  ptr_item *new_item = make_ptr_item(10, 20);
+  bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item);
+  GPR_ASSERT(ok);
+
+  ptr_item *item1 =
+      (ptr_item *)intrusive_hash_map_find(&hash_map, (uint64_t)10);
+  GPR_ASSERT(item1->obj->val == 20);
+  GPR_ASSERT(item1 == new_item);
+
+  ptr_item *item2 =
+      (ptr_item *)intrusive_hash_map_erase(&hash_map, (uint64_t)10);
+  GPR_ASSERT(item2 == new_item);
+
+  gpr_free(new_item->obj);
+  gpr_free(new_item);
+  GPR_ASSERT(0 == intrusive_hash_map_size(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+void test_two_items() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  string_item *new_item1 = make_string_item(10, "test1", 5);
+  bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item1);
+  GPR_ASSERT(ok);
+  string_item *new_item2 = make_string_item(20, "test2", 5);
+  ok = intrusive_hash_map_insert(&hash_map, (hm_item *)new_item2);
+  GPR_ASSERT(ok);
+
+  string_item *item1 =
+      (string_item *)intrusive_hash_map_find(&hash_map, (uint64_t)10);
+  GPR_ASSERT(compare_string_item(new_item1, item1));
+  GPR_ASSERT(item1 == new_item1);
+  string_item *item2 =
+      (string_item *)intrusive_hash_map_find(&hash_map, (uint64_t)20);
+  GPR_ASSERT(compare_string_item(new_item2, item2));
+  GPR_ASSERT(item2 == new_item2);
+
+  item1 = (string_item *)intrusive_hash_map_erase(&hash_map, (uint64_t)10);
+  GPR_ASSERT(item1 == new_item1);
+  item2 = (string_item *)intrusive_hash_map_erase(&hash_map, (uint64_t)20);
+  GPR_ASSERT(item2 == new_item2);
+
+  gpr_free(new_item1);
+  gpr_free(new_item2);
+  GPR_ASSERT(0 == intrusive_hash_map_size(&hash_map));
+  intrusive_hash_map_free(&hash_map, NULL);
+}
+
+// Test resetting and clearing the hash map.
+void test_reset_clear() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  // Add some data to the hash_map.
+  for (uint64_t i = 0; i < 3; ++i) {
+    intrusive_hash_map_insert(&hash_map, (hm_item *)make_ptr_item(i, i));
+  }
+  GPR_ASSERT(3 == intrusive_hash_map_size(&hash_map));
+
+  // Test find.
+  for (uint64_t i = 0; i < 3; ++i) {
+    ptr_item *item = (ptr_item *)intrusive_hash_map_find(&hash_map, i);
+    GPR_ASSERT(item != NULL);
+    GPR_ASSERT(item->IHM_key == i && item->obj->val == i);
+  }
+
+  intrusive_hash_map_clear(&hash_map, &free_ptr_item);
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+// Check that the hash_map contains every key between [min_value, max_value]
+// (inclusive).
+void check_hash_map_values(intrusive_hash_map *hash_map, uint64_t min_value,
+                           uint64_t max_value) {
+  GPR_ASSERT(intrusive_hash_map_size(hash_map) == max_value - min_value + 1);
+
+  for (uint64_t i = min_value; i <= max_value; ++i) {
+    ptr_item *item = (ptr_item *)intrusive_hash_map_find(hash_map, i);
+    GPR_ASSERT(item != NULL);
+    GPR_ASSERT(item->obj->val == i);
+  }
+}
+
+// Add many items and cause the hash_map to extend.
+void test_extend() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+
+  const uint64_t kNumValues = (1 << 16);
+
+  for (uint64_t i = 0; i < kNumValues; ++i) {
+    ptr_item *item = make_ptr_item(i, i);
+    bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)item);
+    GPR_ASSERT(ok);
+    if (i % 1000 == 0) {
+      check_hash_map_values(&hash_map, 0, i);
+    }
+  }
+
+  for (uint64_t i = 0; i < kNumValues; ++i) {
+    ptr_item *item = (ptr_item *)intrusive_hash_map_find(&hash_map, i);
+    GPR_ASSERT(item != NULL);
+    GPR_ASSERT(item->IHM_key == i && item->obj->val == i);
+    ptr_item *item2 = (ptr_item *)intrusive_hash_map_erase(&hash_map, i);
+    GPR_ASSERT(item == item2);
+    gpr_free(item->obj);
+    gpr_free(item);
+  }
+
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+void test_stress() {
+  intrusive_hash_map hash_map;
+  intrusive_hash_map_init(&hash_map, kInitialLog2Size);
+  size_t n = 0;
+
+  // Randomly add and insert entries 1000000 times.
+  for (uint64_t i = 0; i < 1000000; ++i) {
+    int op = rand() & 0x1;
+
+    switch (op) {
+      // Case 0 is insertion of entry.
+      case 0: {
+        uint64_t key = (uint64_t)(rand() % 10000);
+        ptr_item *item = make_ptr_item(key, key);
+        bool ok = intrusive_hash_map_insert(&hash_map, (hm_item *)item);
+        if (ok) {
+          n++;
+        } else {
+          gpr_free(item->obj);
+          gpr_free(item);
+        }
+        break;
+      }
+      // Case 1 is removal of entry.
+      case 1: {
+        uint64_t key = (uint64_t)(rand() % 10000);
+        ptr_item *item = (ptr_item *)intrusive_hash_map_find(&hash_map, key);
+        if (item != NULL) {
+          n--;
+          GPR_ASSERT(key == item->obj->val);
+          ptr_item *item2 =
+              (ptr_item *)intrusive_hash_map_erase(&hash_map, key);
+          GPR_ASSERT(item == item2);
+          gpr_free(item->obj);
+          gpr_free(item);
+        }
+        break;
+      }
+    }
+  }
+  // Check size
+  GPR_ASSERT(n == intrusive_hash_map_size(&hash_map));
+
+  // Clean the hash_map up.
+  intrusive_hash_map_clear(&hash_map, &free_ptr_item);
+  GPR_ASSERT(intrusive_hash_map_empty(&hash_map));
+  intrusive_hash_map_free(&hash_map, &free_ptr_item);
+}
+
+int main(int argc, char **argv) {
+  grpc_test_init(argc, argv);
+  gpr_time_init();
+  srand((unsigned)gpr_now(GPR_CLOCK_REALTIME).tv_nsec);
+
+  test_empty();
+  test_single_item();
+  test_two_items();
+  test_reset_clear();
+  test_extend();
+  test_stress();
+
+  return 0;
+}
diff --git a/test/core/surface/concurrent_connectivity_test.c b/test/core/surface/concurrent_connectivity_test.c
index 87ad095..7614696 100644
--- a/test/core/surface/concurrent_connectivity_test.c
+++ b/test/core/surface/concurrent_connectivity_test.c
@@ -61,6 +61,14 @@
 #define DELAY_MILLIS 10
 #define POLL_MILLIS 3000
 
+#define NUM_OUTER_LOOPS_SHORT_TIMEOUTS 10
+#define NUM_INNER_LOOPS_SHORT_TIMEOUTS 100
+#define DELAY_MILLIS_SHORT_TIMEOUTS 1
+// in a successful test run, POLL_MILLIS should never be reached beause all runs
+// should
+// end after the shorter delay_millis
+#define POLL_MILLIS_SHORT_TIMEOUTS 30000
+
 static void *tag(int n) { return (void *)(uintptr_t)n; }
 static int detag(void *p) { return (int)(uintptr_t)p; }
 
@@ -79,6 +87,8 @@
           grpc_timeout_milliseconds_to_deadline(POLL_MILLIS);
       GPR_ASSERT(grpc_completion_queue_next(cq, poll_time, NULL).type ==
                  GRPC_OP_COMPLETE);
+      /* check that the watcher from "watch state" was free'd */
+      GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(chan) == 0);
     }
     grpc_channel_destroy(chan);
     grpc_completion_queue_destroy(cq);
@@ -168,11 +178,10 @@
   gpr_free(pollset);
 }
 
-int main(int argc, char **argv) {
+int run_concurrent_connectivity_test() {
   struct server_thread_args args;
   memset(&args, 0, sizeof(args));
 
-  grpc_test_init(argc, argv);
   grpc_init();
 
   gpr_thd_id threads[NUM_THREADS];
@@ -242,3 +251,59 @@
   grpc_shutdown();
   return 0;
 }
+
+void watches_with_short_timeouts(void *addr) {
+  for (int i = 0; i < NUM_OUTER_LOOPS_SHORT_TIMEOUTS; ++i) {
+    grpc_completion_queue *cq = grpc_completion_queue_create_for_next(NULL);
+    grpc_channel *chan = grpc_insecure_channel_create((char *)addr, NULL, NULL);
+
+    for (int j = 0; j < NUM_INNER_LOOPS_SHORT_TIMEOUTS; ++j) {
+      gpr_timespec later_time =
+          grpc_timeout_milliseconds_to_deadline(DELAY_MILLIS_SHORT_TIMEOUTS);
+      grpc_connectivity_state state =
+          grpc_channel_check_connectivity_state(chan, 0);
+      GPR_ASSERT(state == GRPC_CHANNEL_IDLE);
+      grpc_channel_watch_connectivity_state(chan, state, later_time, cq, NULL);
+      gpr_timespec poll_time =
+          grpc_timeout_milliseconds_to_deadline(POLL_MILLIS_SHORT_TIMEOUTS);
+      grpc_event ev = grpc_completion_queue_next(cq, poll_time, NULL);
+      GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
+      GPR_ASSERT(ev.success == false);
+      /* check that the watcher from "watch state" was free'd */
+      GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(chan) == 0);
+    }
+    grpc_channel_destroy(chan);
+    grpc_completion_queue_destroy(cq);
+  }
+}
+
+// This test tries to catch deadlock situations.
+// With short timeouts on "watches" and long timeouts on cq next calls,
+// so that a QUEUE_TIMEOUT likely means that something is stuck.
+int run_concurrent_watches_with_short_timeouts_test() {
+  grpc_init();
+
+  gpr_thd_id threads[NUM_THREADS];
+
+  char *localhost = gpr_strdup("localhost:54321");
+  gpr_thd_options options = gpr_thd_options_default();
+  gpr_thd_options_set_joinable(&options);
+
+  for (size_t i = 0; i < NUM_THREADS; ++i) {
+    gpr_thd_new(&threads[i], watches_with_short_timeouts, localhost, &options);
+  }
+  for (size_t i = 0; i < NUM_THREADS; ++i) {
+    gpr_thd_join(threads[i]);
+  }
+  gpr_free(localhost);
+
+  grpc_shutdown();
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  grpc_test_init(argc, argv);
+
+  run_concurrent_connectivity_test();
+  run_concurrent_watches_with_short_timeouts_test();
+}
diff --git a/test/core/surface/num_external_connectivity_watchers_test.c b/test/core/surface/num_external_connectivity_watchers_test.c
new file mode 100644
index 0000000..93944c9
--- /dev/null
+++ b/test/core/surface/num_external_connectivity_watchers_test.c
@@ -0,0 +1,214 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/host_port.h>
+#include <grpc/support/log.h>
+#include <grpc/support/thd.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
+#include "test/core/end2end/data/ssl_test_data.h"
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+
+typedef struct test_fixture {
+  const char *name;
+  grpc_channel *(*create_channel)(const char *addr);
+} test_fixture;
+
+static size_t next_tag = 1;
+
+static void channel_idle_start_watch(grpc_channel *channel,
+                                     grpc_completion_queue *cq) {
+  gpr_timespec connect_deadline = grpc_timeout_milliseconds_to_deadline(1);
+  GPR_ASSERT(grpc_channel_check_connectivity_state(channel, 0) ==
+             GRPC_CHANNEL_IDLE);
+
+  grpc_channel_watch_connectivity_state(
+      channel, GRPC_CHANNEL_IDLE, connect_deadline, cq, (void *)(next_tag++));
+  gpr_log(GPR_DEBUG, "number of active connect watchers: %d",
+          grpc_channel_num_external_connectivity_watchers(channel));
+}
+
+static void channel_idle_poll_for_timeout(grpc_channel *channel,
+                                          grpc_completion_queue *cq) {
+  grpc_event ev =
+      grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
+
+  /* expect watch_connectivity_state to end with a timeout */
+  GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
+  GPR_ASSERT(ev.success == false);
+  GPR_ASSERT(grpc_channel_check_connectivity_state(channel, 0) ==
+             GRPC_CHANNEL_IDLE);
+}
+
+/* Test and use the "num_external_watchers" call to make sure
+ * that "connectivity watcher" structs are free'd just after, if
+ * their corresponding timeouts occur. */
+static void run_timeouts_test(const test_fixture *fixture) {
+  gpr_log(GPR_INFO, "TEST: %s", fixture->name);
+
+  char *addr;
+
+  grpc_init();
+
+  gpr_join_host_port(&addr, "localhost", grpc_pick_unused_port_or_die());
+
+  grpc_channel *channel = fixture->create_channel(addr);
+  grpc_completion_queue *cq = grpc_completion_queue_create_for_next(NULL);
+
+  /* start 1 watcher and then let it time out */
+  channel_idle_start_watch(channel, cq);
+  channel_idle_poll_for_timeout(channel, cq);
+  GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(channel) == 0);
+
+  /* start 3 watchers and then let them all time out */
+  for (size_t i = 1; i <= 3; i++) {
+    channel_idle_start_watch(channel, cq);
+  }
+  for (size_t i = 1; i <= 3; i++) {
+    channel_idle_poll_for_timeout(channel, cq);
+  }
+  GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(channel) == 0);
+
+  /* start 3 watchers, see one time out, start another 3, and then see them all
+   * time out */
+  for (size_t i = 1; i <= 3; i++) {
+    channel_idle_start_watch(channel, cq);
+  }
+  channel_idle_poll_for_timeout(channel, cq);
+  for (size_t i = 3; i <= 5; i++) {
+    channel_idle_start_watch(channel, cq);
+  }
+  for (size_t i = 1; i <= 5; i++) {
+    channel_idle_poll_for_timeout(channel, cq);
+  }
+  GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(channel) == 0);
+
+  grpc_channel_destroy(channel);
+  grpc_completion_queue_shutdown(cq);
+  GPR_ASSERT(
+      grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME), NULL)
+          .type == GRPC_QUEUE_SHUTDOWN);
+  grpc_completion_queue_destroy(cq);
+
+  grpc_shutdown();
+  gpr_free(addr);
+}
+
+/* An edge scenario; sets channel state to explicitly, and outside
+ * of a polling call. */
+static void run_channel_shutdown_before_timeout_test(
+    const test_fixture *fixture) {
+  gpr_log(GPR_INFO, "TEST: %s", fixture->name);
+
+  char *addr;
+
+  grpc_init();
+
+  gpr_join_host_port(&addr, "localhost", grpc_pick_unused_port_or_die());
+
+  grpc_channel *channel = fixture->create_channel(addr);
+  grpc_completion_queue *cq = grpc_completion_queue_create_for_next(NULL);
+
+  /* start 1 watcher and then shut down the channel before the timer goes off */
+  GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(channel) == 0);
+
+  /* expecting a 30 second timeout to go off much later than the shutdown. */
+  gpr_timespec connect_deadline = grpc_timeout_seconds_to_deadline(30);
+  GPR_ASSERT(grpc_channel_check_connectivity_state(channel, 0) ==
+             GRPC_CHANNEL_IDLE);
+
+  grpc_channel_watch_connectivity_state(channel, GRPC_CHANNEL_IDLE,
+                                        connect_deadline, cq, (void *)1);
+  grpc_channel_destroy(channel);
+
+  grpc_event ev =
+      grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
+  GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
+  /* expect success with a state transition to CHANNEL_SHUTDOWN */
+  GPR_ASSERT(ev.success == true);
+
+  grpc_completion_queue_shutdown(cq);
+  GPR_ASSERT(
+      grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME), NULL)
+          .type == GRPC_QUEUE_SHUTDOWN);
+  grpc_completion_queue_destroy(cq);
+
+  grpc_shutdown();
+  gpr_free(addr);
+}
+
+static grpc_channel *insecure_test_create_channel(const char *addr) {
+  return grpc_insecure_channel_create(addr, NULL, NULL);
+}
+
+static const test_fixture insecure_test = {
+    "insecure", insecure_test_create_channel,
+};
+
+static grpc_channel *secure_test_create_channel(const char *addr) {
+  grpc_channel_credentials *ssl_creds =
+      grpc_ssl_credentials_create(test_root_cert, NULL, NULL);
+  grpc_arg ssl_name_override = {GRPC_ARG_STRING,
+                                GRPC_SSL_TARGET_NAME_OVERRIDE_ARG,
+                                {"foo.test.google.fr"}};
+  grpc_channel_args *new_client_args =
+      grpc_channel_args_copy_and_add(NULL, &ssl_name_override, 1);
+  grpc_channel *channel =
+      grpc_secure_channel_create(ssl_creds, addr, new_client_args, NULL);
+  {
+    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+    grpc_channel_args_destroy(&exec_ctx, new_client_args);
+    grpc_exec_ctx_finish(&exec_ctx);
+  }
+  grpc_channel_credentials_release(ssl_creds);
+  return channel;
+}
+
+static const test_fixture secure_test = {
+    "secure", secure_test_create_channel,
+};
+
+int main(int argc, char **argv) {
+  grpc_test_init(argc, argv);
+
+  run_timeouts_test(&insecure_test);
+  run_timeouts_test(&secure_test);
+
+  run_channel_shutdown_before_timeout_test(&insecure_test);
+  run_channel_shutdown_before_timeout_test(&secure_test);
+}
diff --git a/test/core/surface/sequential_connectivity_test.c b/test/core/surface/sequential_connectivity_test.c
index fbecdd7..a0f18e9 100644
--- a/test/core/surface/sequential_connectivity_test.c
+++ b/test/core/surface/sequential_connectivity_test.c
@@ -100,6 +100,9 @@
                                             connect_deadline, cq, NULL);
       grpc_event ev = grpc_completion_queue_next(
           cq, gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
+      /* check that the watcher from "watch state" was free'd */
+      GPR_ASSERT(grpc_channel_num_external_connectivity_watchers(channels[i]) ==
+                 0);
       GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
       GPR_ASSERT(ev.tag == NULL);
       GPR_ASSERT(ev.success == true);
diff --git a/tools/distrib/python/grpcio_tools/grpc_version.py b/tools/distrib/python/grpcio_tools/grpc_version.py
index 1f2aa81..09738ea 100644
--- a/tools/distrib/python/grpcio_tools/grpc_version.py
+++ b/tools/distrib/python/grpcio_tools/grpc_version.py
@@ -29,4 +29,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/tools/distrib/python/grpcio_tools/grpc_version.py.template`!!!
 
-VERSION='1.4.0.dev0'
+VERSION='1.4.0rc1'
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index 3861bdb..d3eb740 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.4.0-dev
+PROJECT_NUMBER         = 1.4.0-pre1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 5bab66c..c87e52d 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.4.0-dev
+PROJECT_NUMBER         = 1.4.0-pre1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.core b/tools/doxygen/Doxyfile.core
index c5ae421..1a8902a 100644
--- a/tools/doxygen/Doxyfile.core
+++ b/tools/doxygen/Doxyfile.core
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 4.0.0-dev
+PROJECT_NUMBER         = 4.0.0-pre1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index eb0883b..49ebc2a 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -40,7 +40,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 4.0.0-dev
+PROJECT_NUMBER         = 4.0.0-pre1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -883,6 +883,9 @@
 src/core/ext/census/grpc_filter.h \
 src/core/ext/census/grpc_plugin.c \
 src/core/ext/census/initialize.c \
+src/core/ext/census/intrusive_hash_map.c \
+src/core/ext/census/intrusive_hash_map.h \
+src/core/ext/census/intrusive_hash_map_internal.h \
 src/core/ext/census/mlog.c \
 src/core/ext/census/mlog.h \
 src/core/ext/census/operation.c \
diff --git a/tools/run_tests/artifacts/build_artifact_node.bat b/tools/run_tests/artifacts/build_artifact_node.bat
index da4d479..72c59a9 100644
--- a/tools/run_tests/artifacts/build_artifact_node.bat
+++ b/tools/run_tests/artifacts/build_artifact_node.bat
@@ -27,7 +27,7 @@
 @rem (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 @rem OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-set node_versions=4.0.0 5.0.0 6.0.0 7.0.0
+set node_versions=4.0.0 5.0.0 6.0.0 7.0.0 8.0.0
 
 set electron_versions=1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0
 
@@ -35,6 +35,8 @@
 
 del /f /q BUILD || rmdir build /s /q
 
+call npm update -g npm
+
 call npm update || goto :error
 
 mkdir -p %ARTIFACTS_OUT%
@@ -45,13 +47,13 @@
 @rem Try again after removing openssl headers
   rmdir "%HOMEDRIVE%%HOMEPATH%\.node-gyp\%%v\include\node\openssl" /S /Q
   rmdir "%HOMEDRIVE%%HOMEPATH%\.node-gyp\iojs-%%v\include\node\openssl" /S /Q
-  call .\node_modules\.bin\node-pre-gyp.cmd build package testpackage --target=%%v --target_arch=%1 || goto :error
+  call .\node_modules\.bin\node-pre-gyp.cmd build package --target=%%v --target_arch=%1 || goto :error
 
   xcopy /Y /I /S build\stage\* %ARTIFACTS_OUT%\ || goto :error
 )
 
 for %%v in (%electron_versions%) do (
-  cmd /V /C "set "HOME=%HOMEDRIVE%%HOMEPATH%\electron-gyp" && call .\node_modules\.bin\node-pre-gyp.cmd configure rebuild package testpackage --runtime=electron --target=%%v --target_arch=%1 --disturl=https://atom.io/download/electron" || goto :error
+  cmd /V /C "set "HOME=%HOMEDRIVE%%HOMEPATH%\electron-gyp" && call .\node_modules\.bin\node-pre-gyp.cmd configure rebuild package --runtime=electron --target=%%v --target_arch=%1 --disturl=https://atom.io/download/electron" || goto :error
 
   xcopy /Y /I /S build\stage\* %ARTIFACTS_OUT%\ || goto :error
 )
diff --git a/tools/run_tests/artifacts/build_artifact_node.sh b/tools/run_tests/artifacts/build_artifact_node.sh
index 3947bde..8061c62 100755
--- a/tools/run_tests/artifacts/build_artifact_node.sh
+++ b/tools/run_tests/artifacts/build_artifact_node.sh
@@ -34,6 +34,8 @@
 nvm use 4
 set -ex
 
+npm update -g npm
+
 cd $(dirname $0)/../../..
 
 rm -rf build || true
@@ -42,18 +44,18 @@
 
 npm update
 
-node_versions=( 4.0.0 5.0.0 6.0.0 7.0.0 )
+node_versions=( 4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 )
 
 electron_versions=( 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 )
 
 for version in ${node_versions[@]}
 do
-  ./node_modules/.bin/node-pre-gyp configure rebuild package testpackage --target=$version --target_arch=$NODE_TARGET_ARCH --grpc_alpine=true
+  ./node_modules/.bin/node-pre-gyp configure rebuild package --target=$version --target_arch=$NODE_TARGET_ARCH --grpc_alpine=true
   cp -r build/stage/* "${ARTIFACTS_OUT}"/
 done
 
 for version in ${electron_versions[@]}
 do
-  HOME=~/.electron-gyp ./node_modules/.bin/node-pre-gyp configure rebuild package testpackage --runtime=electron --target=$version --target_arch=$NODE_TARGET_ARCH --disturl=https://atom.io/download/electron
+  HOME=~/.electron-gyp ./node_modules/.bin/node-pre-gyp configure rebuild package --runtime=electron --target=$version --target_arch=$NODE_TARGET_ARCH --disturl=https://atom.io/download/electron
   cp -r build/stage/* "${ARTIFACTS_OUT}"/
 done
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index ec2836e..6c4462e 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -191,6 +191,23 @@
     "headers": [], 
     "is_filegroup": false, 
     "language": "c", 
+    "name": "census_intrusive_hash_map_test", 
+    "src": [
+      "test/core/census/intrusive_hash_map_test.c"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
     "name": "census_resource_test", 
     "src": [
       "test/core/census/resource_test.c"
@@ -1748,6 +1765,23 @@
     "headers": [], 
     "is_filegroup": false, 
     "language": "c", 
+    "name": "num_external_connectivity_watchers_test", 
+    "src": [
+      "test/core/surface/num_external_connectivity_watchers_test.c"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
     "name": "parse_address_test", 
     "src": [
       "test/core/client_channel/parse_address_test.c"
@@ -7541,6 +7575,8 @@
       "src/core/ext/census/gen/census.pb.h", 
       "src/core/ext/census/gen/trace_context.pb.h", 
       "src/core/ext/census/grpc_filter.h", 
+      "src/core/ext/census/intrusive_hash_map.h", 
+      "src/core/ext/census/intrusive_hash_map_internal.h", 
       "src/core/ext/census/mlog.h", 
       "src/core/ext/census/resource.h", 
       "src/core/ext/census/rpc_metric_id.h", 
@@ -7571,6 +7607,9 @@
       "src/core/ext/census/grpc_filter.h", 
       "src/core/ext/census/grpc_plugin.c", 
       "src/core/ext/census/initialize.c", 
+      "src/core/ext/census/intrusive_hash_map.c", 
+      "src/core/ext/census/intrusive_hash_map.h", 
+      "src/core/ext/census/intrusive_hash_map_internal.h", 
       "src/core/ext/census/mlog.c", 
       "src/core/ext/census/mlog.h", 
       "src/core/ext/census/operation.c", 
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index d1e3a99..1242025 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -237,6 +237,28 @@
     "flaky": false, 
     "gtest": false, 
     "language": "c", 
+    "name": "census_intrusive_hash_map_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ]
+  }, 
+  {
+    "args": [], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c", 
     "name": "census_resource_test", 
     "platforms": [
       "linux", 
@@ -1817,6 +1839,30 @@
     ], 
     "cpu_cost": 1.0, 
     "exclude_configs": [], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c", 
+    "name": "num_external_connectivity_watchers_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ]
+  }, 
+  {
+    "args": [], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
     "exclude_iomgrs": [], 
     "flaky": false, 
     "gtest": false, 
diff --git a/tools/run_tests/helper_scripts/pre_build_node.bat b/tools/run_tests/helper_scripts/pre_build_node.bat
index addb01a..62b883d 100644
--- a/tools/run_tests/helper_scripts/pre_build_node.bat
+++ b/tools/run_tests/helper_scripts/pre_build_node.bat
@@ -29,5 +29,7 @@
 
 set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm
 
-@rem Expire cache after 1 day
-call npm update --cache-min 86400
+@rem Update npm to at least version 5
+call npm update -g npm
+
+call npm update --prefer-online
diff --git a/tools/run_tests/helper_scripts/pre_build_node.sh b/tools/run_tests/helper_scripts/pre_build_node.sh
index 083cb2b..b7c6e14 100755
--- a/tools/run_tests/helper_scripts/pre_build_node.sh
+++ b/tools/run_tests/helper_scripts/pre_build_node.sh
@@ -35,10 +35,12 @@
 nvm install $NODE_VERSION
 set -ex
 
+# Update npm to at least version 5
+npm update -g npm
+
 export GRPC_CONFIG=${CONFIG:-opt}
 
-# Expire cache after 1 day
-npm update --cache-min 86400
+npm update --prefer-online
 
 npm install node-gyp-install
 ./node_modules/.bin/node-gyp-install
diff --git a/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh b/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh
index 6688025..ab882d6 100755
--- a/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh
+++ b/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh
@@ -41,4 +41,5 @@
 ruby src/ruby/end2end/killed_client_thread_driver.rb || EXIT_CODE=1
 ruby src/ruby/end2end/forking_client_driver.rb || EXIT_CODE=1
 ruby src/ruby/end2end/grpc_class_init_driver.rb || EXIT_CODE=1
+ruby src/ruby/end2end/multiple_killed_watching_threads_driver.rb || EXIT_CODE=1
 exit $EXIT_CODE
diff --git a/tools/run_tests/python_utils/jobset.py b/tools/run_tests/python_utils/jobset.py
index 3754035..3ff7733 100755
--- a/tools/run_tests/python_utils/jobset.py
+++ b/tools/run_tests/python_utils/jobset.py
@@ -41,6 +41,7 @@
 import sys
 import tempfile
 import time
+import errno
 
 
 # cpu cost measurement
@@ -132,29 +133,44 @@
 _FORMAT = '%(asctime)-15s %(message)s'
 logging.basicConfig(level=logging.INFO, format=_FORMAT)
 
+
+def eintr_be_gone(fn):
+  """Run fn until it doesn't stop because of EINTR"""
+  while True:
+    try:
+      return fn()
+    except IOError, e:
+      if e.errno != errno.EINTR:
+        raise
+
+
+
 def message(tag, msg, explanatory_text=None, do_newline=False):
   if message.old_tag == tag and message.old_msg == msg and not explanatory_text:
     return
   message.old_tag = tag
   message.old_msg = msg
-  try:
-    if platform_string() == 'windows' or not sys.stdout.isatty():
-      if explanatory_text:
-        logging.info(explanatory_text)
-      logging.info('%s: %s', tag, msg)
-    else:
-      sys.stdout.write('%s%s%s\x1b[%d;%dm%s\x1b[0m: %s%s' % (
-          _BEGINNING_OF_LINE,
-          _CLEAR_LINE,
-          '\n%s' % explanatory_text if explanatory_text is not None else '',
-          _COLORS[_TAG_COLOR[tag]][1],
-          _COLORS[_TAG_COLOR[tag]][0],
-          tag,
-          msg,
-          '\n' if do_newline or explanatory_text is not None else ''))
-    sys.stdout.flush()
-  except:
-    pass
+  while True:
+    try:
+      if platform_string() == 'windows' or not sys.stdout.isatty():
+        if explanatory_text:
+          logging.info(explanatory_text)
+        logging.info('%s: %s', tag, msg)
+      else:
+        sys.stdout.write('%s%s%s\x1b[%d;%dm%s\x1b[0m: %s%s' % (
+            _BEGINNING_OF_LINE,
+            _CLEAR_LINE,
+            '\n%s' % explanatory_text if explanatory_text is not None else '',
+            _COLORS[_TAG_COLOR[tag]][1],
+            _COLORS[_TAG_COLOR[tag]][0],
+            tag,
+            msg,
+            '\n' if do_newline or explanatory_text is not None else ''))
+      sys.stdout.flush()
+      return
+    except IOError, e:
+      if e.errno != errno.EINTR:
+        raise
 
 message.old_tag = ''
 message.old_msg = ''
@@ -226,16 +242,6 @@
     self.cpu_measured = 0
 
 
-def eintr_be_gone(fn):
-  """Run fn until it doesn't stop because of EINTR"""
-  while True:
-    try:
-      return fn()
-    except IOError, e:
-      if e.errno != errno.EINTR:
-        raise
-
-
 def read_from_start(f):
   f.seek(0)
   return f.read()
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 568774c..266d31b 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -431,10 +431,11 @@
     # we should specify in the compiler argument
     _check_compiler(self.args.compiler, ['default', 'node0.12',
                                          'node4', 'node5', 'node6',
-                                         'node7', 'electron1.3', 'electron1.6'])
+                                         'node7', 'node8',
+                                         'electron1.3', 'electron1.6'])
     if self.args.compiler == 'default':
       self.runtime = 'node'
-      self.node_version = '7'
+      self.node_version = '8'
     else:
       if self.args.compiler.startswith('electron'):
         self.runtime = 'electron'
@@ -1173,7 +1174,7 @@
                            'clang3.4', 'clang3.5', 'clang3.6', 'clang3.7',
                            'vs2013', 'vs2015',
                            'python2.7', 'python3.4', 'python3.5', 'python3.6', 'pypy', 'pypy3', 'python_alpine',
-                           'node0.12', 'node4', 'node5', 'node6', 'node7',
+                           'node0.12', 'node4', 'node5', 'node6', 'node7', 'node8',
                            'electron1.3', 'electron1.6',
                            'coreclr',
                            'cmake'],
diff --git a/tools/run_tests/run_tests_matrix.py b/tools/run_tests/run_tests_matrix.py
index 84551d9..32b9d56 100755
--- a/tools/run_tests/run_tests_matrix.py
+++ b/tools/run_tests/run_tests_matrix.py
@@ -309,6 +309,15 @@
                               extra_args=extra_args,
                               inner_jobs=inner_jobs)
 
+  test_jobs += _generate_jobs(languages=['node'],
+                              configs=['dbg'],
+                              platforms=['linux'],
+                              arch='default',
+                              compiler='node7',
+                              labels=['portability'],
+                              extra_args=extra_args,
+                              inner_jobs=inner_jobs)
+
   return test_jobs
 
 
diff --git a/vsprojects/buildtests_c.sln b/vsprojects/buildtests_c.sln
index 97a75e7..b7696a9 100644
--- a/vsprojects/buildtests_c.sln
+++ b/vsprojects/buildtests_c.sln
@@ -129,6 +129,17 @@
 		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
 	EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "census_intrusive_hash_map_test", "vcxproj\test\census_intrusive_hash_map_test\census_intrusive_hash_map_test.vcxproj", "{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}"
+	ProjectSection(myProperties) = preProject
+        	lib = "False"
+	EndProjectSection
+	ProjectSection(ProjectDependencies) = postProject
+		{17BCAFC0-5FDC-4C94-AEB9-95F3E220614B} = {17BCAFC0-5FDC-4C94-AEB9-95F3E220614B}
+		{29D16885-7228-4C31-81ED-5F9187C7F2A9} = {29D16885-7228-4C31-81ED-5F9187C7F2A9}
+		{EAB0A629-17A9-44DB-B5FF-E91A721FE037} = {EAB0A629-17A9-44DB-B5FF-E91A721FE037}
+		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
+	EndProjectSection
+EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "census_resource_test", "vcxproj\test\census_resource_test\census_resource_test.vcxproj", "{18CF99B5-3C61-EC3D-9509-3C95334C3B88}"
 	ProjectSection(myProperties) = preProject
         	lib = "False"
@@ -1339,6 +1350,17 @@
 		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
 	EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "num_external_connectivity_watchers_test", "vcxproj\test\num_external_connectivity_watchers_test\num_external_connectivity_watchers_test.vcxproj", "{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}"
+	ProjectSection(myProperties) = preProject
+        	lib = "False"
+	EndProjectSection
+	ProjectSection(ProjectDependencies) = postProject
+		{17BCAFC0-5FDC-4C94-AEB9-95F3E220614B} = {17BCAFC0-5FDC-4C94-AEB9-95F3E220614B}
+		{29D16885-7228-4C31-81ED-5F9187C7F2A9} = {29D16885-7228-4C31-81ED-5F9187C7F2A9}
+		{EAB0A629-17A9-44DB-B5FF-E91A721FE037} = {EAB0A629-17A9-44DB-B5FF-E91A721FE037}
+		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
+	EndProjectSection
+EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "parse_address_test", "vcxproj\test\parse_address_test\parse_address_test.vcxproj", "{EDEA8257-AEA8-1B0A-F95B-8D6CD7286463}"
 	ProjectSection(myProperties) = preProject
         	lib = "False"
@@ -1899,6 +1921,22 @@
 		{5C1CFC2D-AF3C-D7CB-BA74-D267E91CBC73}.Release-DLL|Win32.Build.0 = Release|Win32
 		{5C1CFC2D-AF3C-D7CB-BA74-D267E91CBC73}.Release-DLL|x64.ActiveCfg = Release|x64
 		{5C1CFC2D-AF3C-D7CB-BA74-D267E91CBC73}.Release-DLL|x64.Build.0 = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|Win32.ActiveCfg = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|x64.ActiveCfg = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|Win32.ActiveCfg = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|x64.ActiveCfg = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|Win32.Build.0 = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug|x64.Build.0 = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|Win32.Build.0 = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release|x64.Build.0 = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|Win32.ActiveCfg = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|Win32.Build.0 = Debug|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|x64.ActiveCfg = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Debug-DLL|x64.Build.0 = Debug|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|Win32.ActiveCfg = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|Win32.Build.0 = Release|Win32
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|x64.ActiveCfg = Release|x64
+		{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}.Release-DLL|x64.Build.0 = Release|x64
 		{18CF99B5-3C61-EC3D-9509-3C95334C3B88}.Debug|Win32.ActiveCfg = Debug|Win32
 		{18CF99B5-3C61-EC3D-9509-3C95334C3B88}.Debug|x64.ActiveCfg = Debug|x64
 		{18CF99B5-3C61-EC3D-9509-3C95334C3B88}.Release|Win32.ActiveCfg = Release|Win32
@@ -3723,6 +3761,22 @@
 		{A66AC548-E2B9-74CD-293C-43526EE51DCE}.Release-DLL|Win32.Build.0 = Release|Win32
 		{A66AC548-E2B9-74CD-293C-43526EE51DCE}.Release-DLL|x64.ActiveCfg = Release|x64
 		{A66AC548-E2B9-74CD-293C-43526EE51DCE}.Release-DLL|x64.Build.0 = Release|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug|Win32.ActiveCfg = Debug|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug|x64.ActiveCfg = Debug|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release|Win32.ActiveCfg = Release|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release|x64.ActiveCfg = Release|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug|Win32.Build.0 = Debug|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug|x64.Build.0 = Debug|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release|Win32.Build.0 = Release|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release|x64.Build.0 = Release|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug-DLL|Win32.ActiveCfg = Debug|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug-DLL|Win32.Build.0 = Debug|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug-DLL|x64.ActiveCfg = Debug|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Debug-DLL|x64.Build.0 = Debug|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release-DLL|Win32.ActiveCfg = Release|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release-DLL|Win32.Build.0 = Release|Win32
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release-DLL|x64.ActiveCfg = Release|x64
+		{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}.Release-DLL|x64.Build.0 = Release|x64
 		{EDEA8257-AEA8-1B0A-F95B-8D6CD7286463}.Debug|Win32.ActiveCfg = Debug|Win32
 		{EDEA8257-AEA8-1B0A-F95B-8D6CD7286463}.Debug|x64.ActiveCfg = Debug|x64
 		{EDEA8257-AEA8-1B0A-F95B-8D6CD7286463}.Release|Win32.ActiveCfg = Release|Win32
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 2ccda23..1303366 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -504,6 +504,8 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\census.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\trace_context.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\resource.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\rpc_metric_id.h" />
@@ -993,6 +995,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\operation.c">
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index 3a1c1f9..9f25a1c 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -712,6 +712,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+      <Filter>src\core\ext\census</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
@@ -1454,6 +1457,12 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index d084d70..ac403a7 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -469,6 +469,8 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\census.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\gen\trace_context.pb.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\resource.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\rpc_metric_id.h" />
@@ -900,6 +902,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\operation.c">
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index ba82685..9fee2ec 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -625,6 +625,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\initialize.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.c">
+      <Filter>src\core\ext\census</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\census\mlog.c">
       <Filter>src\core\ext\census</Filter>
     </ClCompile>
@@ -1289,6 +1292,12 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\grpc_filter.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\intrusive_hash_map_internal.h">
+      <Filter>src\core\ext\census</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\ext\census\mlog.h">
       <Filter>src\core\ext\census</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj
new file mode 100644
index 0000000..46ea06b
--- /dev/null
+++ b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\1.0.204.1.props')" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{BD364959-BDBF-ABD2-C6D1-FC838EBEBB60}</ProjectGuid>
+    <IgnoreWarnIntDirInTempDetected>true</IgnoreWarnIntDirInTempDetected>
+    <IntDir>$(SolutionDir)IntDir\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '10.0'" Label="Configuration">
+    <PlatformToolset>v100</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '11.0'" Label="Configuration">
+    <PlatformToolset>v110</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '12.0'" Label="Configuration">
+    <PlatformToolset>v120</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '14.0'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(SolutionDir)\..\vsprojects\global.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\openssl.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\winsock.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\zlib.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <TargetName>census_intrusive_hash_map_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Debug</Configuration-grpc_dependencies_zlib>
+    <Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl>
+    <Configuration-grpc_dependencies_openssl>Debug</Configuration-grpc_dependencies_openssl>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <TargetName>census_intrusive_hash_map_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Release</Configuration-grpc_dependencies_zlib>
+    <Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl>
+    <Configuration-grpc_dependencies_openssl>Release</Configuration-grpc_dependencies_openssl>
+  </PropertyGroup>
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\test\core\census\intrusive_hash_map_test.c">
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc_test_util\grpc_test_util.vcxproj">
+      <Project>{17BCAFC0-5FDC-4C94-AEB9-95F3E220614B}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc\grpc.vcxproj">
+      <Project>{29D16885-7228-4C31-81ED-5F9187C7F2A9}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr_test_util\gpr_test_util.vcxproj">
+      <Project>{EAB0A629-17A9-44DB-B5FF-E91A721FE037}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr\gpr.vcxproj">
+      <Project>{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" />
+  </Target>
+</Project>
+
diff --git a/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj.filters b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj.filters
new file mode 100644
index 0000000..2dfa300
--- /dev/null
+++ b/vsprojects/vcxproj/test/census_intrusive_hash_map_test/census_intrusive_hash_map_test.vcxproj.filters
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\test\core\census\intrusive_hash_map_test.c">
+      <Filter>test\core\census</Filter>
+    </ClCompile>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Filter Include="test">
+      <UniqueIdentifier>{24bfacc6-bd89-4cdf-4183-3ff53180fa48}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core">
+      <UniqueIdentifier>{71b1debf-71c7-c1e9-9e01-21330ede0d7f}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core\census">
+      <UniqueIdentifier>{0228063a-a601-967e-27ed-9f6197cb3629}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>
+
diff --git a/vsprojects/vcxproj/test/num_external_connectivity_watchers_test/num_external_connectivity_watchers_test.vcxproj b/vsprojects/vcxproj/test/num_external_connectivity_watchers_test/num_external_connectivity_watchers_test.vcxproj
new file mode 100644
index 0000000..2b373e8
--- /dev/null
+++ b/vsprojects/vcxproj/test/num_external_connectivity_watchers_test/num_external_connectivity_watchers_test.vcxproj
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\1.0.204.1.props')" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{4E856E4A-7497-1B1A-1AED-D4C01E5D873A}</ProjectGuid>
+    <IgnoreWarnIntDirInTempDetected>true</IgnoreWarnIntDirInTempDetected>
+    <IntDir>$(SolutionDir)IntDir\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '10.0'" Label="Configuration">
+    <PlatformToolset>v100</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '11.0'" Label="Configuration">
+    <PlatformToolset>v110</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '12.0'" Label="Configuration">
+    <PlatformToolset>v120</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '14.0'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(SolutionDir)\..\vsprojects\global.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\openssl.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\winsock.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\zlib.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <TargetName>num_external_connectivity_watchers_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Debug</Configuration-grpc_dependencies_zlib>
+    <Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl>
+    <Configuration-grpc_dependencies_openssl>Debug</Configuration-grpc_dependencies_openssl>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <TargetName>num_external_connectivity_watchers_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Release</Configuration-grpc_dependencies_zlib>
+    <Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl>
+    <Configuration-grpc_dependencies_openssl>Release</Configuration-grpc_dependencies_openssl>
+  </PropertyGroup>
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+
+    <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+      <MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\test\core\surface\num_external_connectivity_watchers_test.c">
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc_test_util\grpc_test_util.vcxproj">
+      <Project>{17BCAFC0-5FDC-4C94-AEB9-95F3E220614B}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc\grpc.vcxproj">
+      <Project>{29D16885-7228-4C31-81ED-5F9187C7F2A9}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr_test_util\gpr_test_util.vcxproj">
+      <Project>{EAB0A629-17A9-44DB-B5FF-E91A721FE037}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr\gpr.vcxproj">
+      <Project>{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" />
+  <Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" />
+    <Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" />
+  </Target>
+</Project>
+
diff --git a/vsprojects/vcxproj/test/num_external_connectivity_watchers_test/num_external_connectivity_watchers_test.vcxproj.filters b/vsprojects/vcxproj/test/num_external_connectivity_watchers_test/num_external_connectivity_watchers_test.vcxproj.filters
new file mode 100644
index 0000000..92a4198
--- /dev/null
+++ b/vsprojects/vcxproj/test/num_external_connectivity_watchers_test/num_external_connectivity_watchers_test.vcxproj.filters
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\test\core\surface\num_external_connectivity_watchers_test.c">
+      <Filter>test\core\surface</Filter>
+    </ClCompile>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Filter Include="test">
+      <UniqueIdentifier>{9557f01e-947a-775e-4540-bf9a1fd9b19a}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core">
+      <UniqueIdentifier>{2b3a6de2-5820-e21f-5b39-66012c94bfbb}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core\surface">
+      <UniqueIdentifier>{e3f23659-fc16-a4cc-a9e2-c73b625c38f5}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>
+