Merge remote-tracking branch 'upstream/master' into reflection_header
diff --git a/BUILD b/BUILD
index fbfe176..13a6aba 100644
--- a/BUILD
+++ b/BUILD
@@ -217,10 +217,10 @@
     "src/core/lib/iomgr/timer_heap.h",
     "src/core/lib/iomgr/udp_server.h",
     "src/core/lib/iomgr/unix_sockets_posix.h",
+    "src/core/lib/iomgr/wakeup_fd_cv.h",
     "src/core/lib/iomgr/wakeup_fd_pipe.h",
     "src/core/lib/iomgr/wakeup_fd_posix.h",
     "src/core/lib/iomgr/workqueue.h",
-    "src/core/lib/iomgr/workqueue_posix.h",
     "src/core/lib/iomgr/workqueue_windows.h",
     "src/core/lib/json/json.h",
     "src/core/lib/json/json_common.h",
@@ -379,11 +379,11 @@
     "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_posix.c",
     "src/core/lib/iomgr/workqueue_windows.c",
     "src/core/lib/json/json.c",
     "src/core/lib/json/json_reader.c",
@@ -618,10 +618,10 @@
     "src/core/lib/iomgr/timer_heap.h",
     "src/core/lib/iomgr/udp_server.h",
     "src/core/lib/iomgr/unix_sockets_posix.h",
+    "src/core/lib/iomgr/wakeup_fd_cv.h",
     "src/core/lib/iomgr/wakeup_fd_pipe.h",
     "src/core/lib/iomgr/wakeup_fd_posix.h",
     "src/core/lib/iomgr/workqueue.h",
-    "src/core/lib/iomgr/workqueue_posix.h",
     "src/core/lib/iomgr/workqueue_windows.h",
     "src/core/lib/json/json.h",
     "src/core/lib/json/json_common.h",
@@ -765,11 +765,11 @@
     "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_posix.c",
     "src/core/lib/iomgr/workqueue_windows.c",
     "src/core/lib/json/json.c",
     "src/core/lib/json/json_reader.c",
@@ -974,10 +974,10 @@
     "src/core/lib/iomgr/timer_heap.h",
     "src/core/lib/iomgr/udp_server.h",
     "src/core/lib/iomgr/unix_sockets_posix.h",
+    "src/core/lib/iomgr/wakeup_fd_cv.h",
     "src/core/lib/iomgr/wakeup_fd_pipe.h",
     "src/core/lib/iomgr/wakeup_fd_posix.h",
     "src/core/lib/iomgr/workqueue.h",
-    "src/core/lib/iomgr/workqueue_posix.h",
     "src/core/lib/iomgr/workqueue_windows.h",
     "src/core/lib/json/json.h",
     "src/core/lib/json/json_common.h",
@@ -1113,11 +1113,11 @@
     "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_posix.c",
     "src/core/lib/iomgr/workqueue_windows.c",
     "src/core/lib/json/json.c",
     "src/core/lib/json/json_reader.c",
@@ -1823,11 +1823,11 @@
     "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_posix.c",
     "src/core/lib/iomgr/workqueue_windows.c",
     "src/core/lib/json/json.c",
     "src/core/lib/json/json_reader.c",
@@ -2041,10 +2041,10 @@
     "src/core/lib/iomgr/timer_heap.h",
     "src/core/lib/iomgr/udp_server.h",
     "src/core/lib/iomgr/unix_sockets_posix.h",
+    "src/core/lib/iomgr/wakeup_fd_cv.h",
     "src/core/lib/iomgr/wakeup_fd_pipe.h",
     "src/core/lib/iomgr/wakeup_fd_posix.h",
     "src/core/lib/iomgr/workqueue.h",
-    "src/core/lib/iomgr/workqueue_posix.h",
     "src/core/lib/iomgr/workqueue_windows.h",
     "src/core/lib/json/json.h",
     "src/core/lib/json/json_common.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6ea5e06..4a0c251 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -346,11 +346,11 @@
   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_posix.c
   src/core/lib/iomgr/workqueue_windows.c
   src/core/lib/json/json.c
   src/core/lib/json/json_reader.c
@@ -605,11 +605,11 @@
   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_posix.c
   src/core/lib/iomgr/workqueue_windows.c
   src/core/lib/json/json.c
   src/core/lib/json/json_reader.c
@@ -836,11 +836,11 @@
   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_posix.c
   src/core/lib/iomgr/workqueue_windows.c
   src/core/lib/json/json.c
   src/core/lib/json/json_reader.c
diff --git a/Makefile b/Makefile
index 44eb4e6..b4fb861 100644
--- a/Makefile
+++ b/Makefile
@@ -1025,6 +1025,7 @@
 udp_server_test: $(BINDIR)/$(CONFIG)/udp_server_test
 uri_fuzzer_test: $(BINDIR)/$(CONFIG)/uri_fuzzer_test
 uri_parser_test: $(BINDIR)/$(CONFIG)/uri_parser_test
+wakeup_fd_cv_test: $(BINDIR)/$(CONFIG)/wakeup_fd_cv_test
 alarm_cpp_test: $(BINDIR)/$(CONFIG)/alarm_cpp_test
 async_end2end_test: $(BINDIR)/$(CONFIG)/async_end2end_test
 auth_property_iterator_test: $(BINDIR)/$(CONFIG)/auth_property_iterator_test
@@ -1071,6 +1072,7 @@
 secure_auth_context_test: $(BINDIR)/$(CONFIG)/secure_auth_context_test
 secure_sync_unary_ping_pong_test: $(BINDIR)/$(CONFIG)/secure_sync_unary_ping_pong_test
 server_builder_plugin_test: $(BINDIR)/$(CONFIG)/server_builder_plugin_test
+server_context_test_spouse_test: $(BINDIR)/$(CONFIG)/server_context_test_spouse_test
 server_crash_test: $(BINDIR)/$(CONFIG)/server_crash_test
 server_crash_test_client: $(BINDIR)/$(CONFIG)/server_crash_test_client
 shutdown_test: $(BINDIR)/$(CONFIG)/shutdown_test
@@ -1340,6 +1342,7 @@
   $(BINDIR)/$(CONFIG)/transport_security_test \
   $(BINDIR)/$(CONFIG)/udp_server_test \
   $(BINDIR)/$(CONFIG)/uri_parser_test \
+  $(BINDIR)/$(CONFIG)/wakeup_fd_cv_test \
   $(BINDIR)/$(CONFIG)/public_headers_must_be_c89 \
   $(BINDIR)/$(CONFIG)/badreq_bad_client_test \
   $(BINDIR)/$(CONFIG)/connection_prefix_bad_client_test \
@@ -1439,6 +1442,7 @@
   $(BINDIR)/$(CONFIG)/secure_auth_context_test \
   $(BINDIR)/$(CONFIG)/secure_sync_unary_ping_pong_test \
   $(BINDIR)/$(CONFIG)/server_builder_plugin_test \
+  $(BINDIR)/$(CONFIG)/server_context_test_spouse_test \
   $(BINDIR)/$(CONFIG)/server_crash_test \
   $(BINDIR)/$(CONFIG)/server_crash_test_client \
   $(BINDIR)/$(CONFIG)/shutdown_test \
@@ -1526,6 +1530,7 @@
   $(BINDIR)/$(CONFIG)/secure_auth_context_test \
   $(BINDIR)/$(CONFIG)/secure_sync_unary_ping_pong_test \
   $(BINDIR)/$(CONFIG)/server_builder_plugin_test \
+  $(BINDIR)/$(CONFIG)/server_context_test_spouse_test \
   $(BINDIR)/$(CONFIG)/server_crash_test \
   $(BINDIR)/$(CONFIG)/server_crash_test_client \
   $(BINDIR)/$(CONFIG)/shutdown_test \
@@ -1738,6 +1743,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/udp_server_test || ( echo test udp_server_test failed ; exit 1 )
 	$(E) "[RUN]     Testing uri_parser_test"
 	$(Q) $(BINDIR)/$(CONFIG)/uri_parser_test || ( echo test uri_parser_test failed ; exit 1 )
+	$(E) "[RUN]     Testing wakeup_fd_cv_test"
+	$(Q) $(BINDIR)/$(CONFIG)/wakeup_fd_cv_test || ( echo test wakeup_fd_cv_test failed ; exit 1 )
 	$(E) "[RUN]     Testing public_headers_must_be_c89"
 	$(Q) $(BINDIR)/$(CONFIG)/public_headers_must_be_c89 || ( echo test public_headers_must_be_c89 failed ; exit 1 )
 	$(E) "[RUN]     Testing badreq_bad_client_test"
@@ -1828,6 +1835,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/secure_sync_unary_ping_pong_test || ( echo test secure_sync_unary_ping_pong_test failed ; exit 1 )
 	$(E) "[RUN]     Testing server_builder_plugin_test"
 	$(Q) $(BINDIR)/$(CONFIG)/server_builder_plugin_test || ( echo test server_builder_plugin_test failed ; exit 1 )
+	$(E) "[RUN]     Testing server_context_test_spouse_test"
+	$(Q) $(BINDIR)/$(CONFIG)/server_context_test_spouse_test || ( echo test server_context_test_spouse_test failed ; exit 1 )
 	$(E) "[RUN]     Testing server_crash_test"
 	$(Q) $(BINDIR)/$(CONFIG)/server_crash_test || ( echo test server_crash_test failed ; exit 1 )
 	$(E) "[RUN]     Testing shutdown_test"
@@ -2590,11 +2599,11 @@
     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_posix.c \
     src/core/lib/iomgr/workqueue_windows.c \
     src/core/lib/json/json.c \
     src/core/lib/json/json_reader.c \
@@ -2867,11 +2876,11 @@
     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_posix.c \
     src/core/lib/iomgr/workqueue_windows.c \
     src/core/lib/json/json.c \
     src/core/lib/json/json_reader.c \
@@ -3134,11 +3143,11 @@
     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_posix.c \
     src/core/lib/iomgr/workqueue_windows.c \
     src/core/lib/json/json.c \
     src/core/lib/json/json_reader.c \
@@ -3328,11 +3337,11 @@
     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_posix.c \
     src/core/lib/iomgr/workqueue_windows.c \
     src/core/lib/json/json.c \
     src/core/lib/json/json_reader.c \
@@ -3900,6 +3909,55 @@
 $(OBJDIR)/$(CONFIG)/src/cpp/ext/proto_server_reflection_plugin.o: $(GENDIR)/src/proto/grpc/reflection/v1alpha/reflection.pb.cc $(GENDIR)/src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.cc
 
 
+LIBGRPC++_TEST_SRC = \
+    src/cpp/test/server_context_test_spouse.cc \
+
+PUBLIC_HEADERS_CXX += \
+
+LIBGRPC++_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC++_TEST_SRC))))
+
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure libraries if you don't have OpenSSL.
+
+$(LIBDIR)/$(CONFIG)/libgrpc++_test.a: openssl_dep_error
+
+
+else
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build a C++ library if you don't have protobuf - a bit overreached, but still okay.
+
+$(LIBDIR)/$(CONFIG)/libgrpc++_test.a: protobuf_dep_error
+
+
+else
+
+$(LIBDIR)/$(CONFIG)/libgrpc++_test.a: $(ZLIB_DEP) $(OPENSSL_DEP) $(PROTOBUF_DEP) $(LIBGRPC++_TEST_OBJS) 
+	$(E) "[AR]      Creating $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) rm -f $(LIBDIR)/$(CONFIG)/libgrpc++_test.a
+	$(Q) $(AR) $(AROPTS) $(LIBDIR)/$(CONFIG)/libgrpc++_test.a $(LIBGRPC++_TEST_OBJS) 
+ifeq ($(SYSTEM),Darwin)
+	$(Q) ranlib -no_warning_for_no_symbols $(LIBDIR)/$(CONFIG)/libgrpc++_test.a
+endif
+
+
+
+
+endif
+
+endif
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(LIBGRPC++_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 LIBGRPC++_TEST_CONFIG_SRC = \
     test/cpp/util/test_config_cc.cc \
 
@@ -10715,6 +10773,38 @@
 endif
 
 
+WAKEUP_FD_CV_TEST_SRC = \
+    test/core/iomgr/wakeup_fd_cv_test.c \
+
+WAKEUP_FD_CV_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(WAKEUP_FD_CV_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/wakeup_fd_cv_test: openssl_dep_error
+
+else
+
+
+
+$(BINDIR)/$(CONFIG)/wakeup_fd_cv_test: $(WAKEUP_FD_CV_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) $(WAKEUP_FD_CV_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)/wakeup_fd_cv_test
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/iomgr/wakeup_fd_cv_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_wakeup_fd_cv_test: $(WAKEUP_FD_CV_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(WAKEUP_FD_CV_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 ALARM_CPP_TEST_SRC = \
     test/cpp/common/alarm_cpp_test.cc \
 
@@ -12664,6 +12754,49 @@
 endif
 
 
+SERVER_CONTEXT_TEST_SPOUSE_TEST_SRC = \
+    test/cpp/test/server_context_test_spouse_test.cc \
+
+SERVER_CONTEXT_TEST_SPOUSE_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(SERVER_CONTEXT_TEST_SPOUSE_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/server_context_test_spouse_test: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+.
+
+$(BINDIR)/$(CONFIG)/server_context_test_spouse_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/server_context_test_spouse_test: $(PROTOBUF_DEP) $(SERVER_CONTEXT_TEST_SPOUSE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(SERVER_CONTEXT_TEST_SPOUSE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/server_context_test_spouse_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/cpp/test/server_context_test_spouse_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++_test.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_server_context_test_spouse_test: $(SERVER_CONTEXT_TEST_SPOUSE_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(SERVER_CONTEXT_TEST_SPOUSE_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 SERVER_CRASH_TEST_SRC = \
     test/cpp/end2end/server_crash_test.cc \
 
@@ -15623,6 +15756,7 @@
 src/cpp/ext/proto_server_reflection.cc: $(OPENSSL_DEP)
 src/cpp/ext/proto_server_reflection_plugin.cc: $(OPENSSL_DEP)
 src/cpp/server/secure_server_credentials.cc: $(OPENSSL_DEP)
+src/cpp/test/server_context_test_spouse.cc: $(OPENSSL_DEP)
 src/csharp/ext/grpc_csharp_ext.c: $(OPENSSL_DEP)
 test/core/bad_client/bad_client.c: $(OPENSSL_DEP)
 test/core/bad_ssl/server_common.c: $(OPENSSL_DEP)
diff --git a/binding.gyp b/binding.gyp
index 58edda2..6b1c351 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -621,11 +621,11 @@
         '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_posix.c',
         'src/core/lib/iomgr/workqueue_windows.c',
         'src/core/lib/json/json.c',
         'src/core/lib/json/json_reader.c',
diff --git a/build.yaml b/build.yaml
index 728eb0a..c4bc069 100644
--- a/build.yaml
+++ b/build.yaml
@@ -221,10 +221,10 @@
   - src/core/lib/iomgr/timer_heap.h
   - src/core/lib/iomgr/udp_server.h
   - src/core/lib/iomgr/unix_sockets_posix.h
+  - src/core/lib/iomgr/wakeup_fd_cv.h
   - src/core/lib/iomgr/wakeup_fd_pipe.h
   - src/core/lib/iomgr/wakeup_fd_posix.h
   - src/core/lib/iomgr/workqueue.h
-  - src/core/lib/iomgr/workqueue_posix.h
   - src/core/lib/iomgr/workqueue_windows.h
   - src/core/lib/json/json.h
   - src/core/lib/json/json_common.h
@@ -306,11 +306,11 @@
   - 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_posix.c
   - src/core/lib/iomgr/workqueue_windows.c
   - src/core/lib/json/json.c
   - src/core/lib/json/json_reader.c
@@ -1023,6 +1023,15 @@
   - grpc++
   filegroups:
   - grpc++_reflection_proto
+- name: grpc++_test
+  build: test
+  language: c++
+  headers:
+  - include/grpc++/test/server_context_test_spouse.h
+  src:
+  - src/cpp/test/server_context_test_spouse.cc
+  deps:
+  - grpc++
 - name: grpc++_test_config
   build: private
   language: c++
@@ -2594,6 +2603,20 @@
   - grpc
   - gpr_test_util
   - gpr
+- name: wakeup_fd_cv_test
+  build: test
+  language: c
+  src:
+  - test/core/iomgr/wakeup_fd_cv_test.c
+  deps:
+  - grpc_test_util
+  - grpc
+  - gpr_test_util
+  - gpr
+  platforms:
+  - mac
+  - linux
+  - posix
 - name: alarm_cpp_test
   gtest: true
   build: test
@@ -3223,6 +3246,19 @@
   - grpc
   - gpr_test_util
   - gpr
+- name: server_context_test_spouse_test
+  gtest: true
+  build: test
+  language: c++
+  src:
+  - test/cpp/test/server_context_test_spouse_test.cc
+  deps:
+  - grpc_test_util
+  - grpc++_test
+  - grpc++
+  - grpc
+  - gpr_test_util
+  - gpr
 - name: server_crash_test
   gtest: true
   cpu_cost: 0.1
diff --git a/config.m4 b/config.m4
index 0f10365..30e7e2d 100644
--- a/config.m4
+++ b/config.m4
@@ -140,11 +140,11 @@
     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_posix.c \
     src/core/lib/iomgr/workqueue_windows.c \
     src/core/lib/json/json.c \
     src/core/lib/json/json_reader.c \
diff --git a/doc/environment_variables.md b/doc/environment_variables.md
index 74b7862..d02801b 100644
--- a/doc/environment_variables.md
+++ b/doc/environment_variables.md
@@ -51,6 +51,7 @@
   - glb - traces the grpclb load balancer
   - queue_pluck
   - queue_timeout
+  - server_channel - lightweight trace of significant server channel events
   - secure_endpoint - traces bytes flowing through encrypted channels
   - transport_security - traces metadata about secure channel establishment
   - tcp - traces bytes in and out of a channel
@@ -64,4 +65,3 @@
   - DEBUG - log all gRPC messages
   - INFO - log INFO and ERROR message
   - ERROR - log only errors
-
diff --git a/doc/epoll-polling-engine.md b/doc/epoll-polling-engine.md
new file mode 100644
index 0000000..ab7030a
--- /dev/null
+++ b/doc/epoll-polling-engine.md
@@ -0,0 +1,121 @@
+# `epoll`-based pollset implementation in gRPC
+
+Sree Kuchibhotla (sreek@) [May - 2016]
+(Design input from Craig Tiller and David Klempner)
+
+> Status: As of June 2016, this change is implemented and merged.
+
+> * The bulk of the functionality is in: [ev_poll_linux.c](https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/ev_epoll_linux.c)
+> * Pull request: https://github.com/grpc/grpc/pull/6803
+
+## 1. Introduction
+The document talks about the proposed changes to `epoll`-based implementation of pollsets in gRPC. Section-2 gives an overview of the current implementation, Section-3 talks about the problems in the current implementation and finally Section-4 talks about the proposed changes.
+
+## 2. Current `epoll`-based implementation in gRPC
+
+![image](images/old_epoll_impl.png)
+
+**Figure 1: Current implementation**
+
+A gRPC client or a server can have more than one completion queue. Each completion queue creates a pollset.
+
+The gRPC core library does not create any threads[^1] on its own and relies on the application using the gRPC core library to provide the threads. A thread starts to poll for events by calling the gRPC core surface APIs `grpc_completion_queue_next()` or `grpc_completion_queue_pluck()`. More than one thread can call `grpc_completion_queue_next()`on the same completion queue[^2].
+
+A file descriptor can be in more than one completion queue. There are examples in the next section that show how this can happen.
+
+When an event of interest happens in a pollset, multiple threads are woken up and there are no guarantees on which thread actually ends up performing the work i.e executing the callbacks associated with that event. The thread that performs the work finally queues a completion event `grpc_cq_completion` on the appropriate completion queue and "kicks" (i.e wakes ups) the thread that is actually interested in that event (which can be itself - in which case there is no thread hop)
+
+For example, in **Figure 1**, if `fd1` becomes readable, any one of the threads i.e *Threads 1* to *Threads K* or *Thread P*, might be woken up. Let's say *Thread P* was calling a `grpc_completion_queue_pluck()` and was actually interested in the event on `fd1` but *Thread 1* woke up. In this case, *Thread 1* executes the callbacks and finally kicks *Thread P* by signalling `event_fd_P`. *Thread P* wakes up, realizes that there is a new completion event for it and returns from `grpc_completion_queue_pluck()` to its caller.
+
+## 3. Issues in the current architecture
+
+### _Thundering Herds_
+
+If multiple threads concurrently call `epoll_wait()`, we are guaranteed that only one thread is woken up if one of the `fds` in the set becomes readable/writable. However, in our current implementation, the threads do not directly call a blocking `epoll_wait()`[^3]. Instead, they call `poll()` on the set containing `[event_fd`[^4]`, epoll_fd]`. **(see Figure 1)**
+
+Considering the fact that an `fd` can be in multiple `pollsets` and that each `pollset` might have multiple poller threads, it means that whenever an `fd` becomes readable/writable, all the threads in all the `pollsets` (in which that `fd` is present) are woken up.
+
+The performance impact of this would be more conspicuous on the server side. Here are a two examples of thundering herds on the server side.
+
+Example 1: Listening fds on server
+
+* A gRPC server can have multiple server completion queues (i.e completion queues which are used to listen for incoming channels).
+* A gRPC server can also listen on more than one TCP-port.
+* A listening socket is created for each port the gRPC server would be listening on.
+* Every listening socket's fd is added to all the server completion queues' pollsets. (Currently we do not do any sharding of the listening fds across these pollsets).
+
+This means that for every incoming new channel, all the threads waiting on all the pollsets are woken up.
+
+Example 2: New Incoming-channel fds on server
+
+* Currently, every new incoming channel's `fd` (i.e the socket `fd` that is returned by doing an `accept()` on the new incoming channel) is added to all the server completion queues' pollsets [^5]).
+* Clearly, this would also cause all thundering herd problem for every read onthat fd
+
+There are other scenarios especially on the client side where an fd can end up being on multiple pollsets which would cause thundering herds on the clients.
+
+
+## 4. Proposed changes to the current `epoll`-based polling implementation:
+
+The main idea in this proposal is to group 'related' `fds` into a single epoll-based set. This would ensure that only one thread wakes up in case of an event on one of the `fds` in the epoll set.
+
+To accomplish this, we introduce a new abstraction called `polling_island` which will have an epoll set underneath (See **Figure 2** below).  A `polling_island` contains the following:
+
+* `epoll_fd`: The file descriptor of the underlying epoll set
+* `fd_set`: The set of 'fds' in the pollset island i.e in the epoll set (The pollset island merging operation described later requires the list of fds in the pollset island and currently there is no API available to enumerate all the fds in an epoll set)
+* `event_fd`: A level triggered _event fd_ that is used to wake up all the threads waiting on this epoll set (Note: This `event_fd` is added to the underlying epoll set during pollset island creation. This is useful in the pollset island merging operation described later)
+* `merged_to`:  The polling island into which this one merged. See section 4.2 (case 2) for more details on this. Also note that if `merged_to` is set, all the other fields in this polling island are not used anymore
+
+In this new model, only one thread wakes up whenever an event of interest happens in an epoll set.
+
+![drawing](images/new_epoll_impl.png)
+
+**Figure 2: Proposed changes**
+
+### 4.1 Relation between `fd`, `pollset` and `polling_island:`
+
+* An `fd` may belong to multiple `pollsets` but belongs to exactly one `polling_island`
+* A `pollset` belongs to exactly one `polling_island`
+* An `fd` and the `pollset(s`) it belongs to, have same `polling_island`
+
+### 4.2 Algorithm to add an `fd` to a `pollset`
+
+There are two cases to check here:
+
+* **Case 1:** Both `fd` and `pollset` already belong to the same `polling_island`
+    * This is straightforward and nothing really needs to be done here
+* **Case 2:** The `fd `and `pollset` point to different `polling_islands`: In this case we _merge_ both the polling islands i.e:
+    * Add all the `fds` from the smaller `polling_island `to the larger `polling_island` and update the `merged_to` pointer on the smaller island to point to the larger island.
+    * Wake up all the threads waiting on the smaller `polling_island`'s `epoll_fd` (by signalling the `event_fd` on that island) and make them now wait on the larger `polling_island`'s `epoll_fd`
+    * Update `fd` and `pollset` to now point to the larger `polling_island`
+
+### 4.3 Directed wakeups:
+
+The new implementation, just like the current implementation, does not provide us any guarantees that the thread that is woken up is the thread that is actually interested in the event.  So the thread that woke up executes the callbacks and finally has to 'kick' the appropriate polling thread interested in the event.
+
+In the current implementation, every polling thread also had a `event_fd` on which it was listening to and hence waking it up was as simple as signalling that `event_fd`. However, using an `event_fd` also meant that every thread has to use a `poll()` (on `event_fd` and `epoll_fd`) instead of doing an `epoll_wait()` and this resulted in the thundering herd problems described above.
+
+The proposal here is to use signals and kicking a thread would just be sending a signal to that thread. Unfortunately there are only a few signals available on posix systems and most of them have pre-determined behavior leaving only a few signals `SIGUSR1`, `SIGUSR2` and `SIGRTx (SIGRTMIN to SIGRTMAX)` for custom use.
+
+The calling application might have registered other signal handlers for these signals. `We will provide a new API where the applications can "give a signal number" to gRPC library to use for this purpose.
+
+```
+void grpc_use_signal(int signal_num)
+```
+
+If the calling application does not provide a signal number, then the gRPC library will relegate to using a model similar to the current implementation (where every thread does a blocking `poll()` on its `wakeup_fd` and the `epoll_fd`).  The function` psi_wait() `in figure 2 implements this logic.
+
+**>> **(**NOTE**: Or alternatively, we can implement a turnstile polling (i.e having only one thread calling `epoll_wait()` on the epoll set at any time - which all other threads call poll on their `wakeup_fds`) 
+in case of not getting a signal number from the applications.
+
+
+## Notes
+
+[^1]: Only exception is in case of name-resolution
+
+[^2]: However, a `grpc_completion_queue_next()` and `grpc_completion_queue_pluck()` must not be called in parallel on the same completion queue
+
+[^3]: The threads first do a blocking` poll()` with `[wakeup_fd, epoll_fd]`.  If the `poll()` returns due to an event of interest in the epoll set, they then call a non-blocking i.e a zero-timeout `epoll_wait()` on the `epoll_fd`
+
+[^4]: `event_fd` is the linux platform specific implementation of `grpc_wakeup_fd`.  A `wakeup_fd` is used to wake up polling threads typically when the event for which the polling thread is waiting is already completed by some other thread.  It is also used to wake up the polling threads in case of shutdowns or to re-evaluate the poller's interest in the fds to poll (the last scenario is only in case of `poll`-based (not `epoll`-based) implementation of `pollsets`).
+
+[^5]: See more details about the issue here https://github.com/grpc/grpc/issues/5470 and for a proposed fix here: https://github.com/grpc/grpc/pull/6149
diff --git a/doc/images/new_epoll_impl.png b/doc/images/new_epoll_impl.png
new file mode 100644
index 0000000..9ca1f49
--- /dev/null
+++ b/doc/images/new_epoll_impl.png
Binary files differ
diff --git a/doc/images/old_epoll_impl.png b/doc/images/old_epoll_impl.png
new file mode 100644
index 0000000..7ac3df8
--- /dev/null
+++ b/doc/images/old_epoll_impl.png
Binary files differ
diff --git a/doc/interop-test-descriptions.md b/doc/interop-test-descriptions.md
index 666af18..7c153fa 100644
--- a/doc/interop-test-descriptions.md
+++ b/doc/interop-test-descriptions.md
@@ -779,25 +779,38 @@
 
 ### unimplemented_method
 
-Status: Ready for implementation. Blocking beta.
+This test verifies that calling an unimplemented RPC method returns the 
+UNIMPLEMENTED status code.
 
-This test verifies calling unimplemented RPC method returns the UNIMPLEMENTED status code.
+Server features:
+N/A
+
+Procedure:
+* Client calls `grpc.testing.TestService/UnimplementedMethod` with an empty
+  request (defined as `grpc.testing.Empty`):
+
+    ```
+    {
+    }
+    ```
+   
+Client asserts:
+* received status code is 12 (UNIMPLEMENTED)
+
+### unimplemented_service
+
+This test verifies calling an unimplemented server returns the UNIMPLEMENTED
+status code.
 
 Server features:
 N/A
 
 Procedure:
 * Client calls `grpc.testing.UnimplementedService/UnimplementedCall` with an
-  empty request (defined as `grpc.testing.Empty`):
-
-    ```
-    {
-    }
-    ```
+  empty request (defined as `grpc.testing.Empty`)
 
 Client asserts:
 * received status code is 12 (UNIMPLEMENTED)
-* received status message is empty or null/unset
 
 ### cancel_after_begin
 
diff --git a/examples/python/helloworld/run_codegen.py b/examples/python/helloworld/run_codegen.py
deleted file mode 100644
index 4835ec2..0000000
--- a/examples/python/helloworld/run_codegen.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# 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.
-
-"""Runs protoc with the gRPC plugin to generate messages and gRPC stubs."""
-
-from grpc.tools import protoc
-
-protoc.main(
-    (
-	'',
-	'-I../../protos',
-	'--python_out=.',
-	'--grpc_python_out=.',
-	'../../protos/helloworld.proto',
-    )
-)
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 6886bdf..f993212 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -304,10 +304,10 @@
                       'src/core/lib/iomgr/timer_heap.h',
                       'src/core/lib/iomgr/udp_server.h',
                       'src/core/lib/iomgr/unix_sockets_posix.h',
+                      'src/core/lib/iomgr/wakeup_fd_cv.h',
                       'src/core/lib/iomgr/wakeup_fd_pipe.h',
                       'src/core/lib/iomgr/wakeup_fd_posix.h',
                       'src/core/lib/iomgr/workqueue.h',
-                      'src/core/lib/iomgr/workqueue_posix.h',
                       'src/core/lib/iomgr/workqueue_windows.h',
                       'src/core/lib/json/json.h',
                       'src/core/lib/json/json_common.h',
@@ -470,11 +470,11 @@
                       '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_posix.c',
                       'src/core/lib/iomgr/workqueue_windows.c',
                       'src/core/lib/json/json.c',
                       'src/core/lib/json/json_reader.c',
@@ -677,10 +677,10 @@
                               'src/core/lib/iomgr/timer_heap.h',
                               'src/core/lib/iomgr/udp_server.h',
                               'src/core/lib/iomgr/unix_sockets_posix.h',
+                              'src/core/lib/iomgr/wakeup_fd_cv.h',
                               'src/core/lib/iomgr/wakeup_fd_pipe.h',
                               'src/core/lib/iomgr/wakeup_fd_posix.h',
                               'src/core/lib/iomgr/workqueue.h',
-                              'src/core/lib/iomgr/workqueue_posix.h',
                               'src/core/lib/iomgr/workqueue_windows.h',
                               'src/core/lib/json/json.h',
                               'src/core/lib/json/json_common.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index 6079ea2..ab75a04 100755
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -224,10 +224,10 @@
   s.files += %w( src/core/lib/iomgr/timer_heap.h )
   s.files += %w( src/core/lib/iomgr/udp_server.h )
   s.files += %w( src/core/lib/iomgr/unix_sockets_posix.h )
+  s.files += %w( src/core/lib/iomgr/wakeup_fd_cv.h )
   s.files += %w( src/core/lib/iomgr/wakeup_fd_pipe.h )
   s.files += %w( src/core/lib/iomgr/wakeup_fd_posix.h )
   s.files += %w( src/core/lib/iomgr/workqueue.h )
-  s.files += %w( src/core/lib/iomgr/workqueue_posix.h )
   s.files += %w( src/core/lib/iomgr/workqueue_windows.h )
   s.files += %w( src/core/lib/json/json.h )
   s.files += %w( src/core/lib/json/json_common.h )
@@ -390,11 +390,11 @@
   s.files += %w( src/core/lib/iomgr/udp_server.c )
   s.files += %w( src/core/lib/iomgr/unix_sockets_posix.c )
   s.files += %w( src/core/lib/iomgr/unix_sockets_posix_noop.c )
+  s.files += %w( src/core/lib/iomgr/wakeup_fd_cv.c )
   s.files += %w( src/core/lib/iomgr/wakeup_fd_eventfd.c )
   s.files += %w( src/core/lib/iomgr/wakeup_fd_nospecial.c )
   s.files += %w( src/core/lib/iomgr/wakeup_fd_pipe.c )
   s.files += %w( src/core/lib/iomgr/wakeup_fd_posix.c )
-  s.files += %w( src/core/lib/iomgr/workqueue_posix.c )
   s.files += %w( src/core/lib/iomgr/workqueue_windows.c )
   s.files += %w( src/core/lib/json/json.c )
   s.files += %w( src/core/lib/json/json_reader.c )
diff --git a/include/grpc++/impl/codegen/server_context.h b/include/grpc++/impl/codegen/server_context.h
index bce8d2c..975f710 100644
--- a/include/grpc++/impl/codegen/server_context.h
+++ b/include/grpc++/impl/codegen/server_context.h
@@ -86,6 +86,7 @@
 
 namespace testing {
 class InteropServerContextInspector;
+class ServerContextTestSpouse;
 }  // namespace testing
 
 // Interface of server side rpc context.
@@ -173,6 +174,7 @@
 
  private:
   friend class ::grpc::testing::InteropServerContextInspector;
+  friend class ::grpc::testing::ServerContextTestSpouse;
   friend class ::grpc::ServerInterface;
   friend class ::grpc::Server;
   template <class W, class R>
diff --git a/include/grpc++/test/server_context_test_spouse.h b/include/grpc++/test/server_context_test_spouse.h
new file mode 100644
index 0000000..b248285
--- /dev/null
+++ b/include/grpc++/test/server_context_test_spouse.h
@@ -0,0 +1,67 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#ifndef GRPCXX_TEST_SERVER_CONTEXT_TEST_SPOUSE_H
+#define GRPCXX_TEST_SERVER_CONTEXT_TEST_SPOUSE_H
+
+#include <map>
+
+#include <grpc++/server_context.h>
+
+namespace grpc {
+namespace testing {
+
+// A test-only class to access private members and methods of ServerContext.
+class ServerContextTestSpouse {
+ public:
+  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.
+  void AddClientMetadata(const grpc::string& key, const grpc::string& value);
+  std::multimap<grpc::string, grpc::string> GetInitialMetadata() const {
+    return ctx_->initial_metadata_;
+  }
+  std::multimap<grpc::string, grpc::string> GetTrailingMetadata() const {
+    return ctx_->trailing_metadata_;
+  }
+
+ private:
+  ServerContext* ctx_;  // not owned
+  std::multimap<grpc::string, grpc::string> client_metadata_storage_;
+};
+
+}  // namespace testing
+}  // namespace grpc
+
+#endif  // GRPCXX_TEST_SERVER_CONTEXT_TEST_SPOUSE_H
diff --git a/include/grpc/support/alloc.h b/include/grpc/support/alloc.h
index 327a9ff..7209bec 100644
--- a/include/grpc/support/alloc.h
+++ b/include/grpc/support/alloc.h
@@ -48,7 +48,11 @@
   void (*free_fn)(void *ptr);
 } gpr_allocation_functions;
 
-/* malloc, never returns NULL */
+/* malloc.
+ * If size==0, always returns NULL. Otherwise this function never returns NULL.
+ * The pointer returned is suitably aligned for any kind of variable it could
+ * contain.
+ */
 GPRAPI void *gpr_malloc(size_t size);
 /* free */
 GPRAPI void gpr_free(void *ptr);
diff --git a/package.xml b/package.xml
index 7bac695..37103ac 100644
--- a/package.xml
+++ b/package.xml
@@ -231,10 +231,10 @@
     <file baseinstalldir="/" name="src/core/lib/iomgr/timer_heap.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/udp_server.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/unix_sockets_posix.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_cv.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_pipe.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_posix.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/workqueue.h" role="src" />
-    <file baseinstalldir="/" name="src/core/lib/iomgr/workqueue_posix.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/workqueue_windows.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/json/json.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/json/json_common.h" role="src" />
@@ -397,11 +397,11 @@
     <file baseinstalldir="/" name="src/core/lib/iomgr/udp_server.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/unix_sockets_posix.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/unix_sockets_posix_noop.c" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_cv.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_eventfd.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_nospecial.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_pipe.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_posix.c" role="src" />
-    <file baseinstalldir="/" name="src/core/lib/iomgr/workqueue_posix.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/iomgr/workqueue_windows.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/json/json.c" role="src" />
     <file baseinstalldir="/" name="src/core/lib/json/json_reader.c" role="src" />
diff --git a/src/core/ext/client_config/client_channel.c b/src/core/ext/client_config/client_channel.c
index a6056c3..cbf79af 100644
--- a/src/core/ext/client_config/client_channel.c
+++ b/src/core/ext/client_config/client_channel.c
@@ -513,10 +513,14 @@
 
 static void subchannel_ready(grpc_exec_ctx *exec_ctx, void *arg,
                              grpc_error *error) {
-  call_data *calld = arg;
+  grpc_call_element *elem = arg;
+  call_data *calld = elem->call_data;
+  channel_data *chand = elem->channel_data;
   gpr_mu_lock(&calld->mu);
   GPR_ASSERT(calld->creation_phase ==
              GRPC_SUBCHANNEL_CALL_HOLDER_PICKING_SUBCHANNEL);
+  grpc_polling_entity_del_from_pollset_set(exec_ctx, calld->pollent,
+                                           chand->interested_parties);
   calld->creation_phase = GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING;
   if (calld->connected_subchannel == NULL) {
     gpr_atm_no_barrier_store(&calld->subchannel_call, 1);
@@ -564,6 +568,9 @@
   grpc_closure closure;
 } continue_picking_args;
 
+/** Return true if subchannel is available immediately (in which case on_ready
+    should not be called), or false otherwise (in which case on_ready should be
+    called when the subchannel is available). */
 static bool pick_subchannel(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
                             grpc_metadata_batch *initial_metadata,
                             uint32_t initial_metadata_flags,
@@ -629,8 +636,8 @@
     gpr_mu_unlock(&chand->mu);
     // TODO(dgq): make this deadline configurable somehow.
     const grpc_lb_policy_pick_args inputs = {
-        calld->pollent, initial_metadata, initial_metadata_flags,
-        &calld->lb_token_mdelem, gpr_inf_future(GPR_CLOCK_MONOTONIC)};
+        initial_metadata, initial_metadata_flags, &calld->lb_token_mdelem,
+        gpr_inf_future(GPR_CLOCK_MONOTONIC)};
     r = grpc_lb_policy_pick(exec_ctx, lb_policy, &inputs, connected_subchannel,
                             NULL, on_ready);
     GRPC_LB_POLICY_UNREF(exec_ctx, lb_policy, "pick_subchannel");
@@ -672,6 +679,7 @@
                                          grpc_call_element *elem,
                                          grpc_transport_stream_op *op) {
   call_data *calld = elem->call_data;
+  channel_data *chand = elem->channel_data;
   GRPC_CALL_LOG_OP(GPR_INFO, elem, op);
   grpc_deadline_state_client_start_transport_stream_op(exec_ctx, elem, op);
   /* try to (atomically) get the call */
@@ -739,14 +747,20 @@
       calld->connected_subchannel == NULL &&
       op->send_initial_metadata != NULL) {
     calld->creation_phase = GRPC_SUBCHANNEL_CALL_HOLDER_PICKING_SUBCHANNEL;
-    grpc_closure_init(&calld->next_step, subchannel_ready, calld);
+    grpc_closure_init(&calld->next_step, subchannel_ready, elem);
     GRPC_CALL_STACK_REF(calld->owning_call, "pick_subchannel");
+    /* If a subchannel is not available immediately, the polling entity from
+       call_data should be provided to channel_data's interested_parties, so
+       that IO of the lb_policy and resolver could be done under it. */
     if (pick_subchannel(exec_ctx, elem, op->send_initial_metadata,
                         op->send_initial_metadata_flags,
                         &calld->connected_subchannel, &calld->next_step,
                         GRPC_ERROR_NONE)) {
       calld->creation_phase = GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING;
       GRPC_CALL_STACK_UNREF(exec_ctx, calld->owning_call, "pick_subchannel");
+    } else {
+      grpc_polling_entity_add_to_pollset_set(exec_ctx, calld->pollent,
+                                             chand->interested_parties);
     }
   }
   /* if we've got a subchannel, then let's ask it to create a call */
diff --git a/src/core/ext/client_config/lb_policy.h b/src/core/ext/client_config/lb_policy.h
index 110d08f..de424cd 100644
--- a/src/core/ext/client_config/lb_policy.h
+++ b/src/core/ext/client_config/lb_policy.h
@@ -35,7 +35,6 @@
 #define GRPC_CORE_EXT_CLIENT_CONFIG_LB_POLICY_H
 
 #include "src/core/ext/client_config/subchannel.h"
-#include "src/core/lib/iomgr/polling_entity.h"
 #include "src/core/lib/transport/connectivity_state.h"
 
 /** A load balancing policy: specified by a vtable and a struct (which
@@ -55,8 +54,6 @@
 
 /** Extra arguments for an LB pick */
 typedef struct grpc_lb_policy_pick_args {
-  /** Parties interested in the pick's progress */
-  grpc_polling_entity *pollent;
   /** Initial metadata associated with the picking call. */
   grpc_metadata_batch *initial_metadata;
   /** Bitmask used for selective cancelling. See \a
@@ -153,7 +150,8 @@
     once the pick is complete with its error argument set to indicate
     success or failure.
 
-    Any I/O should be done under \a pick_args->pollent. */
+    Any IO should be done under the \a interested_parties \a grpc_pollset_set
+    in the \a grpc_lb_policy struct. */
 int grpc_lb_policy_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy,
                         const grpc_lb_policy_pick_args *pick_args,
                         grpc_connected_subchannel **target, void **user_data,
diff --git a/src/core/ext/client_config/subchannel.c b/src/core/ext/client_config/subchannel.c
index 0bbaa3e..4cece2d 100644
--- a/src/core/ext/client_config/subchannel.c
+++ b/src/core/ext/client_config/subchannel.c
@@ -220,8 +220,8 @@
                             : gpr_atm_no_barrier_fetch_add(&c->ref_pair, delta);
 #ifdef GRPC_STREAM_REFCOUNT_DEBUG
   gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-          "SUBCHANNEL: %p %12s 0x%08d -> 0x%08d [%s]", c, purpose, (int)old_val,
-          (int)(old_val + delta), reason);
+          "SUBCHANNEL: %p %s 0x%08" PRIxPTR " -> 0x%08" PRIxPTR " [%s]", c,
+          purpose, old_val, old_val + delta, reason);
 #endif
   return old_val;
 }
diff --git a/src/core/ext/lb_policy/grpclb/grpclb.c b/src/core/ext/lb_policy/grpclb/grpclb.c
index 626f285..9af92b7 100644
--- a/src/core/ext/lb_policy/grpclb/grpclb.c
+++ b/src/core/ext/lb_policy/grpclb/grpclb.c
@@ -69,8 +69,8 @@
  * possible scenarios:
  *
  * 1. This is the first server list received. There was no previous instance of
- *    the Round Robin policy. \a rr_handover() will instantiate the RR policy
- *    and perform all the pending operations over it.
+ *    the Round Robin policy. \a rr_handover_locked() will instantiate the RR
+ *    policy and perform all the pending operations over it.
  * 2. There's already a RR policy instance active. We need to introduce the new
  *    one build from the new serverlist, but taking care not to disrupt the
  *    operations in progress over the old RR instance. This is done by
@@ -78,7 +78,7 @@
  *    references are held on the old RR policy, it'll be destroyed and \a
  *    glb_rr_connectivity_changed notified with a \a GRPC_CHANNEL_SHUTDOWN
  *    state. At this point we can transition to a new RR instance safely, which
- *    is done once again via \a rr_handover().
+ *    is done once again via \a rr_handover_locked().
  *
  *
  * Once a RR policy instance is in place (and getting updated as described),
@@ -86,8 +86,8 @@
  * forwarding them to the RR instance. Any time there's no RR policy available
  * (ie, right after the creation of the gRPCLB policy, if an empty serverlist
  * is received, etc), pick/ping requests are added to a list of pending
- * picks/pings to be flushed and serviced as part of \a rr_handover() the moment
- * the RR policy instance becomes available.
+ * picks/pings to be flushed and serviced as part of \a rr_handover_locked() the
+ * moment the RR policy instance becomes available.
  *
  * \see https://github.com/grpc/grpc/blob/master/doc/load-balancing.md for the
  * high level design and details. */
@@ -134,6 +134,9 @@
 }
 
 typedef struct wrapped_rr_closure_arg {
+  /* the closure instance using this struct as argument */
+  grpc_closure wrapper_closure;
+
   /* the original closure. Usually a on_complete/notify cb for pick() and ping()
    * calls against the internal RR instance, respectively. */
   grpc_closure *wrapped_closure;
@@ -155,9 +158,8 @@
   /* The RR instance related to the closure */
   grpc_lb_policy *rr_policy;
 
-  /* when not NULL, represents a pending_{pick,ping} node to be freed upon
-   * closure execution */
-  void *owning_pending_node; /* to be freed if not NULL */
+  /* heap memory to be freed upon closure execution. */
+  void *free_when_done;
 } wrapped_rr_closure_arg;
 
 /* The \a on_complete closure passed as part of the pick requires keeping a
@@ -183,10 +185,10 @@
     }
   }
   GPR_ASSERT(wc_arg->wrapped_closure != NULL);
-
   grpc_exec_ctx_sched(exec_ctx, wc_arg->wrapped_closure, GRPC_ERROR_REF(error),
                       NULL);
-  gpr_free(wc_arg->owning_pending_node);
+  GPR_ASSERT(wc_arg->free_when_done != NULL);
+  gpr_free(wc_arg->free_when_done);
 }
 
 /* Linked list of pending pick requests. It stores all information needed to
@@ -207,10 +209,6 @@
    * upon error. */
   grpc_connected_subchannel **target;
 
-  /* a closure wrapping the original on_complete one to be invoked once the
-   * pick() has completed (regardless of success) */
-  grpc_closure wrapped_on_complete;
-
   /* args for wrapped_on_complete */
   wrapped_rr_closure_arg wrapped_on_complete_arg;
 } pending_pick;
@@ -230,8 +228,9 @@
   pp->wrapped_on_complete_arg.initial_metadata = pick_args->initial_metadata;
   pp->wrapped_on_complete_arg.lb_token_mdelem_storage =
       pick_args->lb_token_mdelem_storage;
-  grpc_closure_init(&pp->wrapped_on_complete, wrapped_rr_closure,
-                    &pp->wrapped_on_complete_arg);
+  pp->wrapped_on_complete_arg.free_when_done = pp;
+  grpc_closure_init(&pp->wrapped_on_complete_arg.wrapper_closure,
+                    wrapped_rr_closure, &pp->wrapped_on_complete_arg);
   *root = pp;
 }
 
@@ -239,10 +238,6 @@
 typedef struct pending_ping {
   struct pending_ping *next;
 
-  /* a closure wrapping the original on_complete one to be invoked once the
-   * ping() has completed (regardless of success) */
-  grpc_closure wrapped_notify;
-
   /* args for wrapped_notify */
   wrapped_rr_closure_arg wrapped_notify_arg;
 } pending_ping;
@@ -251,10 +246,11 @@
   pending_ping *pping = gpr_malloc(sizeof(*pping));
   memset(pping, 0, sizeof(pending_ping));
   memset(&pping->wrapped_notify_arg, 0, sizeof(wrapped_rr_closure_arg));
-  pping->next = *root;
-  grpc_closure_init(&pping->wrapped_notify, wrapped_rr_closure,
-                    &pping->wrapped_notify_arg);
   pping->wrapped_notify_arg.wrapped_closure = notify;
+  pping->wrapped_notify_arg.free_when_done = pping;
+  pping->next = *root;
+  grpc_closure_init(&pping->wrapped_notify_arg.wrapper_closure,
+                    wrapped_rr_closure, &pping->wrapped_notify_arg);
   *root = pping;
 }
 
@@ -307,13 +303,6 @@
 
   /** for tracking of the RR connectivity */
   rr_connectivity_data *rr_connectivity;
-
-  /* a wrapped (see \a wrapped_rr_closure) on-complete closure for readily
-   * available RR picks */
-  grpc_closure wrapped_on_complete;
-
-  /* arguments for the wrapped_on_complete closure */
-  wrapped_rr_closure_arg wc_arg;
 } glb_lb_policy;
 
 /* Keeps track and reacts to changes in connectivity of the RR instance */
@@ -399,14 +388,14 @@
           GPR_ARRAY_SIZE(server->load_balance_token) - 1;
       grpc_mdstr *lb_token_mdstr = grpc_mdstr_from_buffer(
           (uint8_t *)server->load_balance_token, lb_token_size);
-      user_data = grpc_mdelem_from_metadata_strings(
-          GRPC_MDSTR_LOAD_REPORTING_INITIAL, lb_token_mdstr);
+      user_data = grpc_mdelem_from_metadata_strings(GRPC_MDSTR_LB_TOKEN,
+                                                    lb_token_mdstr);
     } else {
       gpr_log(GPR_ERROR,
               "Missing LB token for backend address '%s'. The empty token will "
               "be used instead",
               grpc_sockaddr_to_uri((struct sockaddr *)&addr.addr));
-      user_data = GRPC_MDELEM_LOAD_REPORTING_INITIAL_EMPTY;
+      user_data = GRPC_MDELEM_LB_TOKEN_EMPTY;
     }
 
     grpc_lb_addresses_set_address(lb_addresses, addr_idx, &addr.addr, addr.len,
@@ -424,9 +413,43 @@
   if (token != NULL) GRPC_MDELEM_UNREF(token);
 }
 
-static grpc_lb_policy *create_rr(grpc_exec_ctx *exec_ctx,
-                                 const grpc_grpclb_serverlist *serverlist,
-                                 glb_lb_policy *glb_policy) {
+/* perform a pick over \a rr_policy. Given that a pick can return immediately
+ * (ignoring its completion callback) we need to perform the cleanups this
+ * callback would be otherwise resposible for */
+static bool pick_from_internal_rr_locked(
+    grpc_exec_ctx *exec_ctx, grpc_lb_policy *rr_policy,
+    const grpc_lb_policy_pick_args *pick_args,
+    grpc_connected_subchannel **target, wrapped_rr_closure_arg *wc_arg) {
+  GPR_ASSERT(rr_policy != NULL);
+  const bool pick_done =
+      grpc_lb_policy_pick(exec_ctx, rr_policy, pick_args, target,
+                          (void **)&wc_arg->lb_token, &wc_arg->wrapper_closure);
+  if (pick_done) {
+    /* synchronous grpc_lb_policy_pick call. Unref the RR policy. */
+    if (grpc_lb_glb_trace) {
+      gpr_log(GPR_INFO, "Unreffing RR (0x%" PRIxPTR ")",
+              (intptr_t)wc_arg->rr_policy);
+    }
+    GRPC_LB_POLICY_UNREF(exec_ctx, wc_arg->rr_policy, "glb_pick");
+
+    /* add the load reporting initial metadata */
+    initial_metadata_add_lb_token(pick_args->initial_metadata,
+                                  pick_args->lb_token_mdelem_storage,
+                                  GRPC_MDELEM_REF(wc_arg->lb_token));
+
+    gpr_free(wc_arg);
+  }
+  /* else, the pending pick will be registered and taken care of by the
+   * pending pick list inside the RR policy (glb_policy->rr_policy).
+   * Eventually, wrapped_on_complete will be called, which will -among other
+   * things- add the LB token to the call's initial metadata */
+
+  return pick_done;
+}
+
+static grpc_lb_policy *create_rr_locked(
+    grpc_exec_ctx *exec_ctx, const grpc_grpclb_serverlist *serverlist,
+    glb_lb_policy *glb_policy) {
   GPR_ASSERT(serverlist != NULL && serverlist->num_servers > 0);
 
   grpc_lb_policy_args args;
@@ -446,18 +469,21 @@
   return rr;
 }
 
-static void rr_handover(grpc_exec_ctx *exec_ctx, glb_lb_policy *glb_policy,
-                        grpc_error *error) {
+static void rr_handover_locked(grpc_exec_ctx *exec_ctx,
+                               glb_lb_policy *glb_policy, grpc_error *error) {
   GPR_ASSERT(glb_policy->serverlist != NULL &&
              glb_policy->serverlist->num_servers > 0);
   glb_policy->rr_policy =
-      create_rr(exec_ctx, glb_policy->serverlist, glb_policy);
+      create_rr_locked(exec_ctx, glb_policy->serverlist, glb_policy);
 
   if (grpc_lb_glb_trace) {
     gpr_log(GPR_INFO, "Created RR policy (0x%" PRIxPTR ")",
             (intptr_t)glb_policy->rr_policy);
   }
   GPR_ASSERT(glb_policy->rr_policy != NULL);
+  grpc_pollset_set_add_pollset_set(exec_ctx,
+                                   glb_policy->rr_policy->interested_parties,
+                                   glb_policy->base.interested_parties);
   glb_policy->rr_connectivity->state = grpc_lb_policy_check_connectivity(
       exec_ctx, glb_policy->rr_policy, &error);
   grpc_lb_policy_notify_on_state_change(
@@ -478,11 +504,9 @@
       gpr_log(GPR_INFO, "Pending pick about to PICK from 0x%" PRIxPTR "",
               (intptr_t)glb_policy->rr_policy);
     }
-    grpc_lb_policy_pick(exec_ctx, glb_policy->rr_policy, &pp->pick_args,
-                        pp->target,
-                        (void **)&pp->wrapped_on_complete_arg.lb_token,
-                        &pp->wrapped_on_complete);
-    pp->wrapped_on_complete_arg.owning_pending_node = pp;
+    pick_from_internal_rr_locked(exec_ctx, glb_policy->rr_policy,
+                                 &pp->pick_args, pp->target,
+                                 &pp->wrapped_on_complete_arg);
   }
 
   pending_ping *pping;
@@ -495,8 +519,7 @@
               (intptr_t)glb_policy->rr_policy);
     }
     grpc_lb_policy_ping_one(exec_ctx, glb_policy->rr_policy,
-                            &pping->wrapped_notify);
-    pping->wrapped_notify_arg.owning_pending_node = pping;
+                            &pping->wrapped_notify_arg.wrapper_closure);
   }
 }
 
@@ -509,13 +532,16 @@
     if (glb_policy->serverlist != NULL) {
       /* a RR policy is shutting down but there's a serverlist available ->
        * perform a handover */
-      rr_handover(exec_ctx, glb_policy, error);
+      gpr_mu_lock(&glb_policy->mu);
+      rr_handover_locked(exec_ctx, glb_policy, error);
+      gpr_mu_unlock(&glb_policy->mu);
     } else {
       /* shutting down and no new serverlist available. Bail out. */
       gpr_free(rr_conn_data);
     }
   } else {
     if (error == GRPC_ERROR_NONE) {
+      gpr_mu_lock(&glb_policy->mu);
       /* RR not shutting down. Mimic the RR's policy state */
       grpc_connectivity_state_set(exec_ctx, &glb_policy->state_tracker,
                                   rr_conn_data->state, GRPC_ERROR_REF(error),
@@ -524,6 +550,7 @@
       grpc_lb_policy_notify_on_state_change(exec_ctx, glb_policy->rr_policy,
                                             &rr_conn_data->state,
                                             &rr_conn_data->on_change);
+      gpr_mu_unlock(&glb_policy->mu);
     } else { /* error */
       gpr_free(rr_conn_data);
     }
@@ -648,15 +675,15 @@
   while (pp != NULL) {
     pending_pick *next = pp->next;
     *pp->target = NULL;
-    grpc_exec_ctx_sched(exec_ctx, &pp->wrapped_on_complete, GRPC_ERROR_NONE,
-                        NULL);
+    grpc_exec_ctx_sched(exec_ctx, &pp->wrapped_on_complete_arg.wrapper_closure,
+                        GRPC_ERROR_NONE, NULL);
     pp = next;
   }
 
   while (pping != NULL) {
     pending_ping *next = pping->next;
-    grpc_exec_ctx_sched(exec_ctx, &pping->wrapped_notify, GRPC_ERROR_NONE,
-                        NULL);
+    grpc_exec_ctx_sched(exec_ctx, &pping->wrapped_notify_arg.wrapper_closure,
+                        GRPC_ERROR_NONE, NULL);
     pping = next;
   }
 
@@ -686,11 +713,9 @@
   while (pp != NULL) {
     pending_pick *next = pp->next;
     if (pp->target == target) {
-      grpc_polling_entity_del_from_pollset_set(
-          exec_ctx, pp->pick_args.pollent, glb_policy->base.interested_parties);
       *target = NULL;
       grpc_exec_ctx_sched(
-          exec_ctx, &pp->wrapped_on_complete,
+          exec_ctx, &pp->wrapped_on_complete_arg.wrapper_closure,
           GRPC_ERROR_CREATE_REFERENCING("Pick Cancelled", &error, 1), NULL);
     } else {
       pp->next = glb_policy->pending_picks;
@@ -719,10 +744,8 @@
     pending_pick *next = pp->next;
     if ((pp->pick_args.initial_metadata_flags & initial_metadata_flags_mask) ==
         initial_metadata_flags_eq) {
-      grpc_polling_entity_del_from_pollset_set(
-          exec_ctx, pp->pick_args.pollent, glb_policy->base.interested_parties);
       grpc_exec_ctx_sched(
-          exec_ctx, &pp->wrapped_on_complete,
+          exec_ctx, &pp->wrapped_on_complete_arg.wrapper_closure,
           GRPC_ERROR_CREATE_REFERENCING("Pick Cancelled", &error, 1), NULL);
     } else {
       pp->next = glb_policy->pending_picks;
@@ -775,39 +798,20 @@
               (intptr_t)glb_policy->rr_policy);
     }
     GRPC_LB_POLICY_REF(glb_policy->rr_policy, "glb_pick");
-    memset(&glb_policy->wc_arg, 0, sizeof(wrapped_rr_closure_arg));
-    glb_policy->wc_arg.rr_policy = glb_policy->rr_policy;
-    glb_policy->wc_arg.target = target;
-    glb_policy->wc_arg.wrapped_closure = on_complete;
-    glb_policy->wc_arg.lb_token_mdelem_storage =
-        pick_args->lb_token_mdelem_storage;
-    glb_policy->wc_arg.initial_metadata = pick_args->initial_metadata;
-    glb_policy->wc_arg.owning_pending_node = NULL;
-    grpc_closure_init(&glb_policy->wrapped_on_complete, wrapped_rr_closure,
-                      &glb_policy->wc_arg);
 
-    pick_done =
-        grpc_lb_policy_pick(exec_ctx, glb_policy->rr_policy, pick_args, target,
-                            (void **)&glb_policy->wc_arg.lb_token,
-                            &glb_policy->wrapped_on_complete);
-    if (pick_done) {
-      /* synchronous grpc_lb_policy_pick call. Unref the RR policy. */
-      if (grpc_lb_glb_trace) {
-        gpr_log(GPR_INFO, "Unreffing RR (0x%" PRIxPTR ")",
-                (intptr_t)glb_policy->wc_arg.rr_policy);
-      }
-      GRPC_LB_POLICY_UNREF(exec_ctx, glb_policy->wc_arg.rr_policy, "glb_pick");
+    wrapped_rr_closure_arg *wc_arg = gpr_malloc(sizeof(wrapped_rr_closure_arg));
+    memset(wc_arg, 0, sizeof(wrapped_rr_closure_arg));
 
-      /* add the load reporting initial metadata */
-      initial_metadata_add_lb_token(
-          pick_args->initial_metadata, pick_args->lb_token_mdelem_storage,
-          GRPC_MDELEM_REF(glb_policy->wc_arg.lb_token));
-    }
+    grpc_closure_init(&wc_arg->wrapper_closure, wrapped_rr_closure, wc_arg);
+    wc_arg->rr_policy = glb_policy->rr_policy;
+    wc_arg->target = target;
+    wc_arg->wrapped_closure = on_complete;
+    wc_arg->lb_token_mdelem_storage = pick_args->lb_token_mdelem_storage;
+    wc_arg->initial_metadata = pick_args->initial_metadata;
+    wc_arg->free_when_done = wc_arg;
+    pick_done = pick_from_internal_rr_locked(exec_ctx, glb_policy->rr_policy,
+                                             pick_args, target, wc_arg);
   } else {
-    /* else, the pending pick will be registered and taken care of by the
-     * pending pick list inside the RR policy (glb_policy->rr_policy) */
-    grpc_polling_entity_add_to_pollset_set(exec_ctx, pick_args->pollent,
-                                           glb_policy->base.interested_parties);
     add_pending_pick(&glb_policy->pending_picks, pick_args, target,
                      on_complete);
 
@@ -931,7 +935,7 @@
 
   /* Note the following LB call progresses every time there's activity in \a
    * glb_policy->base.interested_parties, which is comprised of the polling
-   * entities passed to glb_pick(). */
+   * entities from \a client_channel. */
   lb_client->lb_call = grpc_channel_create_pollset_set_call(
       glb_policy->lb_channel, NULL, GRPC_PROPAGATE_DEFAULTS,
       glb_policy->base.interested_parties,
@@ -1076,6 +1080,7 @@
 
       /* update serverlist */
       if (serverlist->num_servers > 0) {
+        gpr_mu_lock(&lb_client->glb_policy->mu);
         if (grpc_grpclb_serverlist_equals(lb_client->glb_policy->serverlist,
                                           serverlist)) {
           if (grpc_lb_glb_trace) {
@@ -1093,7 +1098,7 @@
         if (lb_client->glb_policy->rr_policy == NULL) {
           /* initial "handover", in this case from a null RR policy, meaning
            * it'll just create the first RR policy instance */
-          rr_handover(exec_ctx, lb_client->glb_policy, error);
+          rr_handover_locked(exec_ctx, lb_client->glb_policy, error);
         } else {
           /* unref the RR policy, eventually leading to its substitution with a
            * new one constructed from the received serverlist (see
@@ -1101,6 +1106,7 @@
           GRPC_LB_POLICY_UNREF(exec_ctx, lb_client->glb_policy->rr_policy,
                                "serverlist_received");
         }
+        gpr_mu_unlock(&lb_client->glb_policy->mu);
       } else {
         if (grpc_lb_glb_trace) {
           gpr_log(GPR_INFO,
diff --git a/src/core/ext/lb_policy/pick_first/pick_first.c b/src/core/ext/lb_policy/pick_first/pick_first.c
index 961a0c9..6533327 100644
--- a/src/core/ext/lb_policy/pick_first/pick_first.c
+++ b/src/core/ext/lb_policy/pick_first/pick_first.c
@@ -39,7 +39,6 @@
 
 typedef struct pending_pick {
   struct pending_pick *next;
-  grpc_polling_entity *pollent;
   uint32_t initial_metadata_flags;
   grpc_connected_subchannel **target;
   grpc_closure *on_complete;
@@ -119,8 +118,6 @@
   while (pp != NULL) {
     pending_pick *next = pp->next;
     *pp->target = NULL;
-    grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
-                                             p->base.interested_parties);
     grpc_exec_ctx_sched(exec_ctx, pp->on_complete, GRPC_ERROR_NONE, NULL);
     gpr_free(pp);
     pp = next;
@@ -138,8 +135,6 @@
   while (pp != NULL) {
     pending_pick *next = pp->next;
     if (pp->target == target) {
-      grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
-                                               p->base.interested_parties);
       *target = NULL;
       grpc_exec_ctx_sched(
           exec_ctx, pp->on_complete,
@@ -168,8 +163,6 @@
     pending_pick *next = pp->next;
     if ((pp->initial_metadata_flags & initial_metadata_flags_mask) ==
         initial_metadata_flags_eq) {
-      grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
-                                               p->base.interested_parties);
       grpc_exec_ctx_sched(
           exec_ctx, pp->on_complete,
           GRPC_ERROR_CREATE_REFERENCING("Pick Cancelled", &error, 1), NULL);
@@ -229,11 +222,8 @@
     if (!p->started_picking) {
       start_picking(exec_ctx, p);
     }
-    grpc_polling_entity_add_to_pollset_set(exec_ctx, pick_args->pollent,
-                                           p->base.interested_parties);
     pp = gpr_malloc(sizeof(*pp));
     pp->next = p->pending_picks;
-    pp->pollent = pick_args->pollent;
     pp->target = target;
     pp->initial_metadata_flags = pick_args->initial_metadata_flags;
     pp->on_complete = on_complete;
@@ -319,8 +309,6 @@
         while ((pp = p->pending_picks)) {
           p->pending_picks = pp->next;
           *pp->target = selected;
-          grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
-                                                   p->base.interested_parties);
           grpc_exec_ctx_sched(exec_ctx, pp->on_complete, GRPC_ERROR_NONE, NULL);
           gpr_free(pp);
         }
diff --git a/src/core/ext/lb_policy/round_robin/round_robin.c b/src/core/ext/lb_policy/round_robin/round_robin.c
index 930fa86..9bd3f9d 100644
--- a/src/core/ext/lb_policy/round_robin/round_robin.c
+++ b/src/core/ext/lb_policy/round_robin/round_robin.c
@@ -78,9 +78,6 @@
 typedef struct pending_pick {
   struct pending_pick *next;
 
-  /* polling entity for the pick()'s async notification */
-  grpc_polling_entity *pollent;
-
   /* output argument where to store the pick()ed user_data. It'll be NULL if no
    * such data is present or there's an error (the definite test for errors is
    * \a target being NULL). */
@@ -318,8 +315,6 @@
   while (pp != NULL) {
     pending_pick *next = pp->next;
     if (pp->target == target) {
-      grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
-                                               p->base.interested_parties);
       *target = NULL;
       grpc_exec_ctx_sched(
           exec_ctx, pp->on_complete,
@@ -348,8 +343,6 @@
     pending_pick *next = pp->next;
     if ((pp->initial_metadata_flags & initial_metadata_flags_mask) ==
         initial_metadata_flags_eq) {
-      grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
-                                               p->base.interested_parties);
       *pp->target = NULL;
       grpc_exec_ctx_sched(
           exec_ctx, pp->on_complete,
@@ -403,7 +396,6 @@
   gpr_mu_lock(&p->mu);
   if ((selected = peek_next_connected_locked(p))) {
     /* readily available, report right away */
-    gpr_mu_unlock(&p->mu);
     *target = grpc_subchannel_get_connected_subchannel(selected->subchannel);
 
     if (user_data != NULL) {
@@ -416,17 +408,15 @@
     }
     /* only advance the last picked pointer if the selection was used */
     advance_last_picked_locked(p);
+    gpr_mu_unlock(&p->mu);
     return 1;
   } else {
     /* no pick currently available. Save for later in list of pending picks */
     if (!p->started_picking) {
       start_picking(exec_ctx, p);
     }
-    grpc_polling_entity_add_to_pollset_set(exec_ctx, pick_args->pollent,
-                                           p->base.interested_parties);
     pp = gpr_malloc(sizeof(*pp));
     pp->next = p->pending_picks;
-    pp->pollent = pick_args->pollent;
     pp->target = target;
     pp->on_complete = on_complete;
     pp->initial_metadata_flags = pick_args->initial_metadata_flags;
@@ -482,8 +472,6 @@
                     "[RR CONN CHANGED] TARGET <-- SUBCHANNEL %p (NODE %p)",
                     (void *)selected->subchannel, (void *)selected);
           }
-          grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
-                                                   p->base.interested_parties);
           grpc_exec_ctx_sched(exec_ctx, pp->on_complete, GRPC_ERROR_NONE, NULL);
           gpr_free(pp);
         }
diff --git a/src/core/ext/load_reporting/load_reporting.h b/src/core/ext/load_reporting/load_reporting.h
index e37817d..e130976 100644
--- a/src/core/ext/load_reporting/load_reporting.h
+++ b/src/core/ext/load_reporting/load_reporting.h
@@ -37,13 +37,21 @@
 #include <grpc/impl/codegen/grpc_types.h>
 #include "src/core/lib/channel/channel_stack.h"
 
-/** Metadata key for initial metadata coming from clients */
-/* TODO(dgq): change to the final value TBD */
-#define GRPC_LOAD_REPORTING_INITIAL_MD_KEY "load-reporting-initial"
+/** Metadata key for the gRPC LB load balancer token.
+ *
+ * The value corresponding to this key is an opaque token that is given to the
+ * frontend as part of each pick; the frontend sends this token to the backend
+ * in each request it sends when using that pick. The token is used by the
+ * backend to verify the request and to allow the backend to report load to the
+ * gRPC LB system. */
+#define GRPC_LB_TOKEN_MD_KEY "lb-token"
 
-/** Metadata key for trailing metadata from servers */
-/* TODO(dgq): change to the final value TBD */
-#define GRPC_LOAD_REPORTING_TRAILING_MD_KEY "load-reporting-trailing"
+/** Metadata key for gRPC LB cost reporting.
+ *
+ * The value corresponding to this key is an opaque binary blob reported by the
+ * backend as part of its trailing metadata containing cost information for the
+ * call. */
+#define GRPC_LB_COST_MD_KEY "lb-cost"
 
 /** Identifiers for the invocation point of the users LR callback */
 typedef enum grpc_load_reporting_source {
diff --git a/src/core/ext/load_reporting/load_reporting_filter.c b/src/core/ext/load_reporting/load_reporting_filter.c
index 394f0cb..22bf363 100644
--- a/src/core/ext/load_reporting/load_reporting_filter.c
+++ b/src/core/ext/load_reporting/load_reporting_filter.c
@@ -75,7 +75,7 @@
 
   if (md->key == GRPC_MDSTR_PATH) {
     calld->service_method = grpc_mdstr_as_c_string(md->value);
-  } else if (md->key == GRPC_MDSTR_LOAD_REPORTING_INITIAL) {
+  } else if (md->key == GRPC_MDSTR_LB_TOKEN) {
     calld->initial_md_string = gpr_strdup(grpc_mdstr_as_c_string(md->value));
     return NULL;
   }
@@ -193,7 +193,7 @@
   grpc_call_element *elem = user_data;
   call_data *calld = elem->call_data;
 
-  if (md->key == GRPC_MDSTR_LOAD_REPORTING_TRAILING) {
+  if (md->key == GRPC_MDSTR_LB_COST) {
     calld->trailing_md_string = gpr_strdup(grpc_mdstr_as_c_string(md->value));
     return NULL;
   }
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_plugin.c b/src/core/ext/transport/chttp2/transport/chttp2_plugin.c
index 7d5279b..bd87253 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_plugin.c
+++ b/src/core/ext/transport/chttp2/transport/chttp2_plugin.c
@@ -36,14 +36,11 @@
 #include "src/core/lib/debug/trace.h"
 #include "src/core/lib/transport/metadata.h"
 
-extern int grpc_http_write_state_trace;
-
 void grpc_chttp2_plugin_init(void) {
   grpc_chttp2_base64_encode_and_huffman_compress =
       grpc_chttp2_base64_encode_and_huffman_compress_impl;
   grpc_register_tracer("http", &grpc_http_trace);
   grpc_register_tracer("flowctl", &grpc_flowctl_trace);
-  grpc_register_tracer("http_write_state", &grpc_http_write_state_trace);
 }
 
 void grpc_chttp2_plugin_shutdown(void) {}
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.c b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
index 1dd7fef..97780d9 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.c
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
@@ -65,91 +65,59 @@
 #define MAX_CLIENT_STREAM_ID 0x7fffffffu
 int grpc_http_trace = 0;
 int grpc_flowctl_trace = 0;
-int grpc_http_write_state_trace = 0;
-
-#define TRANSPORT_FROM_WRITING(tw)                                        \
-  ((grpc_chttp2_transport *)((char *)(tw)-offsetof(grpc_chttp2_transport, \
-                                                   writing)))
-
-#define TRANSPORT_FROM_PARSING(tp)                                        \
-  ((grpc_chttp2_transport *)((char *)(tp)-offsetof(grpc_chttp2_transport, \
-                                                   parsing)))
-
-#define TRANSPORT_FROM_GLOBAL(tg)                                         \
-  ((grpc_chttp2_transport *)((char *)(tg)-offsetof(grpc_chttp2_transport, \
-                                                   global)))
-
-#define STREAM_FROM_GLOBAL(sg) \
-  ((grpc_chttp2_stream *)((char *)(sg)-offsetof(grpc_chttp2_stream, global)))
-
-#define STREAM_FROM_PARSING(sg) \
-  ((grpc_chttp2_stream *)((char *)(sg)-offsetof(grpc_chttp2_stream, parsing)))
 
 static const grpc_transport_vtable vtable;
 
 /* forward declarations of various callbacks that we'll build closures around */
-static void writing_action(grpc_exec_ctx *exec_ctx, void *t, grpc_error *error);
-static void reading_action(grpc_exec_ctx *exec_ctx, void *t, grpc_error *error);
-static void parsing_action(grpc_exec_ctx *exec_ctx, void *t, grpc_error *error);
-static void reading_action_locked(grpc_exec_ctx *exec_ctx, void *arg,
-                                  grpc_error *error);
-static void post_parse_locked(grpc_exec_ctx *exec_ctx, void *arg,
-                              grpc_error *error);
-static void initiate_writing_locked(grpc_exec_ctx *exec_ctx, void *t,
+static void write_action_begin_locked(grpc_exec_ctx *exec_ctx, void *t,
+                                      grpc_error *error);
+static void write_action(grpc_exec_ctx *exec_ctx, void *t, grpc_error *error);
+static void write_action_end(grpc_exec_ctx *exec_ctx, void *t,
+                             grpc_error *error);
+static void write_action_end_locked(grpc_exec_ctx *exec_ctx, void *t,
                                     grpc_error *error);
-static void initiate_read_flush_locked(grpc_exec_ctx *exec_ctx, void *t,
-                                       grpc_error *error);
-static void terminate_writing_with_lock(grpc_exec_ctx *exec_ctx, void *t,
-                                        grpc_error *error);
 
-static void start_writing(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t);
-static void end_waiting_for_write(grpc_exec_ctx *exec_ctx,
-                                  grpc_chttp2_transport *t, grpc_error *error);
+static void read_action_begin(grpc_exec_ctx *exec_ctx, void *t,
+                              grpc_error *error);
+static void read_action_locked(grpc_exec_ctx *exec_ctx, void *t,
+                               grpc_error *error);
 
+static void complete_fetch_locked(grpc_exec_ctx *exec_ctx, void *gs,
+                                  grpc_error *error);
+static void complete_fetch(grpc_exec_ctx *exec_ctx, void *gs,
+                           grpc_error *error);
 /** Set a transport level setting, and push it to our peer */
 static void push_setting(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
                          grpc_chttp2_setting_id id, uint32_t value);
 
-/** Start disconnection chain */
-static void drop_connection(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
-                            grpc_error *error);
-
-/** Cancel a stream: coming from the transport API */
-static void cancel_from_api(grpc_exec_ctx *exec_ctx,
-                            grpc_chttp2_transport_global *transport_global,
-                            grpc_chttp2_stream_global *stream_global,
-                            grpc_error *error);
-
-static void close_from_api(grpc_exec_ctx *exec_ctx,
-                           grpc_chttp2_transport_global *transport_global,
-                           grpc_chttp2_stream_global *stream_global,
-                           grpc_error *error);
+static void close_from_api(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                           grpc_chttp2_stream *s, grpc_error *error);
 
 /** Start new streams that have been created if we can */
-static void maybe_start_some_streams(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global);
+static void maybe_start_some_streams(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t);
 
-static void connectivity_state_set(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_connectivity_state state, grpc_error *error, const char *reason);
+static void connectivity_state_set(grpc_exec_ctx *exec_ctx,
+                                   grpc_chttp2_transport *t,
+                                   grpc_connectivity_state state,
+                                   grpc_error *error, const char *reason);
 
-static void check_read_ops(grpc_exec_ctx *exec_ctx,
-                           grpc_chttp2_transport_global *transport_global);
-
-static void incoming_byte_stream_update_flow_control(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, size_t max_size_hint,
-    size_t have_already);
+static void incoming_byte_stream_update_flow_control(grpc_exec_ctx *exec_ctx,
+                                                     grpc_chttp2_transport *t,
+                                                     grpc_chttp2_stream *s,
+                                                     size_t max_size_hint,
+                                                     size_t have_already);
 static void incoming_byte_stream_destroy_locked(grpc_exec_ctx *exec_ctx,
                                                 void *byte_stream,
                                                 grpc_error *error_ignored);
 static void fail_pending_writes(grpc_exec_ctx *exec_ctx,
-                                grpc_chttp2_transport_global *transport_global,
-                                grpc_chttp2_stream_global *stream_global,
+                                grpc_chttp2_transport *t, grpc_chttp2_stream *s,
                                 grpc_error *error);
 
-static void set_write_state(grpc_chttp2_transport *t,
-                            grpc_chttp2_write_state state, const char *reason);
+static void close_transport_locked(grpc_exec_ctx *exec_ctx,
+                                   grpc_chttp2_transport *t, grpc_error *error);
+static void end_all_the_calls(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                              grpc_error *error);
 
 /*******************************************************************************
  * CONSTRUCTION/DESTRUCTION/REFCOUNTING
@@ -161,34 +129,31 @@
 
   grpc_endpoint_destroy(exec_ctx, t->ep);
 
-  gpr_slice_buffer_destroy(&t->global.qbuf);
+  gpr_slice_buffer_destroy(&t->qbuf);
 
-  gpr_slice_buffer_destroy(&t->writing.outbuf);
-  grpc_chttp2_hpack_compressor_destroy(&t->writing.hpack_compressor);
+  gpr_slice_buffer_destroy(&t->outbuf);
+  grpc_chttp2_hpack_compressor_destroy(&t->hpack_compressor);
 
-  gpr_slice_buffer_destroy(&t->parsing.qbuf);
   gpr_slice_buffer_destroy(&t->read_buffer);
-  grpc_chttp2_hpack_parser_destroy(&t->parsing.hpack_parser);
-  grpc_chttp2_goaway_parser_destroy(&t->parsing.goaway_parser);
+  grpc_chttp2_hpack_parser_destroy(&t->hpack_parser);
+  grpc_chttp2_goaway_parser_destroy(&t->goaway_parser);
 
   for (i = 0; i < STREAM_LIST_COUNT; i++) {
     GPR_ASSERT(t->lists[i].head == NULL);
     GPR_ASSERT(t->lists[i].tail == NULL);
   }
 
-  GPR_ASSERT(grpc_chttp2_stream_map_size(&t->parsing_stream_map) == 0);
-  GPR_ASSERT(grpc_chttp2_stream_map_size(&t->new_stream_map) == 0);
+  GPR_ASSERT(grpc_chttp2_stream_map_size(&t->stream_map) == 0);
 
-  grpc_chttp2_stream_map_destroy(&t->parsing_stream_map);
-  grpc_chttp2_stream_map_destroy(&t->new_stream_map);
+  grpc_chttp2_stream_map_destroy(&t->stream_map);
   grpc_connectivity_state_destroy(exec_ctx, &t->channel_callback.state_tracker);
 
-  grpc_combiner_destroy(exec_ctx, t->executor.combiner);
+  grpc_combiner_destroy(exec_ctx, t->combiner);
 
   /* callback remaining pings: they're not allowed to call into the transpot,
      and maybe they hold resources that need to be freed */
-  while (t->global.pings.next != &t->global.pings) {
-    grpc_chttp2_outstanding_ping *ping = t->global.pings.next;
+  while (t->pings.next != &t->pings) {
+    grpc_chttp2_outstanding_ping *ping = t->pings.next;
     grpc_exec_ctx_sched(exec_ctx, ping->on_recv,
                         GRPC_ERROR_CREATE("Transport closed"), NULL);
     ping->next->prev = ping->prev;
@@ -196,37 +161,40 @@
     gpr_free(ping);
   }
 
+  while (t->write_cb_pool) {
+    grpc_chttp2_write_cb *next = t->write_cb_pool->next;
+    gpr_free(t->write_cb_pool);
+    t->write_cb_pool = next;
+  }
+
   gpr_free(t->peer_string);
   gpr_free(t);
 }
 
-/*#define REFCOUNTING_DEBUG 1*/
-#ifdef REFCOUNTING_DEBUG
-#define REF_TRANSPORT(t, r) ref_transport(t, r, __FILE__, __LINE__)
-#define UNREF_TRANSPORT(cl, t, r) unref_transport(cl, t, r, __FILE__, __LINE__)
-static void unref_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
-                            const char *reason, const char *file, int line) {
-  gpr_log(GPR_DEBUG, "chttp2:unref:%p %d->%d %s [%s:%d]", t, t->refs.count,
-          t->refs.count - 1, reason, file, line);
+#ifdef GRPC_CHTTP2_REFCOUNTING_DEBUG
+void grpc_chttp2_unref_transport(grpc_exec_ctx *exec_ctx,
+                                 grpc_chttp2_transport *t, const char *reason,
+                                 const char *file, int line) {
+  gpr_log(GPR_DEBUG, "chttp2:unref:%p %" PRIdPTR "->%" PRIdPTR " %s [%s:%d]", t,
+          t->refs.count, t->refs.count - 1, reason, file, line);
   if (!gpr_unref(&t->refs)) return;
   destruct_transport(exec_ctx, t);
 }
 
-static void ref_transport(grpc_chttp2_transport *t, const char *reason,
-                          const char *file, int line) {
-  gpr_log(GPR_DEBUG, "chttp2:  ref:%p %d->%d %s [%s:%d]", t, t->refs.count,
-          t->refs.count + 1, reason, file, line);
+void grpc_chttp2_ref_transport(grpc_chttp2_transport *t, const char *reason,
+                               const char *file, int line) {
+  gpr_log(GPR_DEBUG, "chttp2:  ref:%p %" PRIdPTR "->%" PRIdPTR " %s [%s:%d]", t,
+          t->refs.count, t->refs.count + 1, reason, file, line);
   gpr_ref(&t->refs);
 }
 #else
-#define REF_TRANSPORT(t, r) ref_transport(t)
-#define UNREF_TRANSPORT(cl, t, r) unref_transport(cl, t)
-static void unref_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) {
+void grpc_chttp2_unref_transport(grpc_exec_ctx *exec_ctx,
+                                 grpc_chttp2_transport *t) {
   if (!gpr_unref(&t->refs)) return;
   destruct_transport(exec_ctx, t);
 }
 
-static void ref_transport(grpc_chttp2_transport *t) { gpr_ref(&t->refs); }
+void grpc_chttp2_ref_transport(grpc_chttp2_transport *t) { gpr_ref(&t->refs); }
 #endif
 
 static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
@@ -241,51 +209,41 @@
   memset(t, 0, sizeof(*t));
 
   t->base.vtable = &vtable;
-  t->executor.write_state = GRPC_CHTTP2_WRITES_CORKED;
   t->ep = ep;
   /* one ref is for destroy */
   gpr_ref_init(&t->refs, 1);
-  /* ref is dropped at transport close() */
-  gpr_ref_init(&t->shutdown_ep_refs, 1);
-  t->executor.combiner = grpc_combiner_create(grpc_endpoint_get_workqueue(ep));
+  t->combiner = grpc_combiner_create(grpc_endpoint_get_workqueue(ep));
   t->peer_string = grpc_endpoint_get_peer(ep);
   t->endpoint_reading = 1;
-  t->global.next_stream_id = is_client ? 1 : 2;
-  t->global.is_client = is_client;
-  t->writing.outgoing_window = DEFAULT_WINDOW;
-  t->parsing.incoming_window = DEFAULT_WINDOW;
-  t->global.stream_lookahead = DEFAULT_WINDOW;
-  t->global.connection_window_target = DEFAULT_CONNECTION_WINDOW_TARGET;
-  t->global.ping_counter = 1;
-  t->global.pings.next = t->global.pings.prev = &t->global.pings;
-  t->parsing.is_client = is_client;
-  t->parsing.deframe_state =
-      is_client ? GRPC_DTS_FH_0 : GRPC_DTS_CLIENT_PREFIX_0;
-  t->parsing.is_first_frame = true;
-  t->writing.is_client = is_client;
+  t->next_stream_id = is_client ? 1 : 2;
+  t->is_client = is_client;
+  t->outgoing_window = DEFAULT_WINDOW;
+  t->incoming_window = DEFAULT_WINDOW;
+  t->stream_lookahead = DEFAULT_WINDOW;
+  t->connection_window_target = DEFAULT_CONNECTION_WINDOW_TARGET;
+  t->ping_counter = 1;
+  t->pings.next = t->pings.prev = &t->pings;
+  t->deframe_state = is_client ? GRPC_DTS_FH_0 : GRPC_DTS_CLIENT_PREFIX_0;
+  t->is_first_frame = true;
   grpc_connectivity_state_init(
       &t->channel_callback.state_tracker, GRPC_CHANNEL_READY,
       is_client ? "client_transport" : "server_transport");
 
-  gpr_slice_buffer_init(&t->global.qbuf);
+  gpr_slice_buffer_init(&t->qbuf);
 
-  gpr_slice_buffer_init(&t->writing.outbuf);
-  grpc_chttp2_hpack_compressor_init(&t->writing.hpack_compressor);
-  grpc_closure_init(&t->writing_action, writing_action, t);
-  grpc_closure_init(&t->reading_action, reading_action, t);
-  grpc_closure_init(&t->reading_action_locked, reading_action_locked, t);
-  grpc_closure_init(&t->parsing_action, parsing_action, t);
-  grpc_closure_init(&t->post_parse_locked, post_parse_locked, t);
-  grpc_closure_init(&t->initiate_writing, initiate_writing_locked, t);
-  grpc_closure_init(&t->terminate_writing, terminate_writing_with_lock, t);
-  grpc_closure_init(&t->initiate_read_flush_locked, initiate_read_flush_locked,
+  gpr_slice_buffer_init(&t->outbuf);
+  grpc_chttp2_hpack_compressor_init(&t->hpack_compressor);
+
+  grpc_closure_init(&t->write_action_begin_locked, write_action_begin_locked,
                     t);
-  grpc_closure_init(&t->writing.done_cb, grpc_chttp2_terminate_writing,
-                    &t->writing);
+  grpc_closure_init(&t->write_action, write_action, t);
+  grpc_closure_init(&t->write_action_end, write_action_end, t);
+  grpc_closure_init(&t->write_action_end_locked, write_action_end_locked, t);
+  grpc_closure_init(&t->read_action_begin, read_action_begin, t);
+  grpc_closure_init(&t->read_action_locked, read_action_locked, t);
 
-  gpr_slice_buffer_init(&t->parsing.qbuf);
-  grpc_chttp2_goaway_parser_init(&t->parsing.goaway_parser);
-  grpc_chttp2_hpack_parser_init(&t->parsing.hpack_parser);
+  grpc_chttp2_goaway_parser_init(&t->goaway_parser);
+  grpc_chttp2_hpack_parser_init(&t->hpack_parser);
 
   gpr_slice_buffer_init(&t->read_buffer);
 
@@ -294,28 +252,24 @@
      large enough that the exponential growth should happen nicely when it's
      needed.
      TODO(ctiller): tune this */
-  grpc_chttp2_stream_map_init(&t->parsing_stream_map, 8);
-  grpc_chttp2_stream_map_init(&t->new_stream_map, 8);
+  grpc_chttp2_stream_map_init(&t->stream_map, 8);
 
   /* copy in initial settings to all setting sets */
   for (i = 0; i < GRPC_CHTTP2_NUM_SETTINGS; i++) {
-    t->parsing.settings[i] = grpc_chttp2_settings_parameters[i].default_value;
     for (j = 0; j < GRPC_NUM_SETTING_SETS; j++) {
-      t->global.settings[j][i] =
-          grpc_chttp2_settings_parameters[i].default_value;
+      t->settings[j][i] = grpc_chttp2_settings_parameters[i].default_value;
     }
   }
-  t->global.dirtied_local_settings = 1;
+  t->dirtied_local_settings = 1;
   /* Hack: it's common for implementations to assume 65536 bytes initial send
      window -- this should by rights be 0 */
-  t->global.force_send_settings = 1 << GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
-  t->global.sent_local_settings = 0;
+  t->force_send_settings = 1 << GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  t->sent_local_settings = 0;
 
   if (is_client) {
-    gpr_slice_buffer_add(
-        &t->writing.outbuf,
-        gpr_slice_from_copied_string(GRPC_CHTTP2_CLIENT_CONNECT_STRING));
-    grpc_chttp2_initiate_write(exec_ctx, &t->global, false, "initial_write");
+    gpr_slice_buffer_add(&t->outbuf, gpr_slice_from_copied_string(
+                                         GRPC_CHTTP2_CLIENT_CONNECT_STRING));
+    grpc_chttp2_initiate_write(exec_ctx, t, false, "initial_write");
   }
 
   /* configure http2 the way we like it */
@@ -330,34 +284,18 @@
 
   if (channel_args) {
     for (i = 0; i < channel_args->num_args; i++) {
-      if (0 ==
-          strcmp(channel_args->args[i].key, GRPC_ARG_MAX_CONCURRENT_STREAMS)) {
-        if (is_client) {
-          gpr_log(GPR_ERROR, "%s: is ignored on the client",
-                  GRPC_ARG_MAX_CONCURRENT_STREAMS);
-        } else {
-          const grpc_integer_options options = {-1, 0, INT_MAX};
-          const int value =
-              grpc_channel_arg_get_integer(&channel_args->args[i], options);
-          if (value >= 0) {
-            push_setting(exec_ctx, t,
-                         GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
-                         (uint32_t)value);
-          }
-        }
-      } else if (0 == strcmp(channel_args->args[i].key,
-                             GRPC_ARG_HTTP2_INITIAL_SEQUENCE_NUMBER)) {
+      if (0 == strcmp(channel_args->args[i].key,
+                      GRPC_ARG_HTTP2_INITIAL_SEQUENCE_NUMBER)) {
         const grpc_integer_options options = {-1, 0, INT_MAX};
         const int value =
             grpc_channel_arg_get_integer(&channel_args->args[i], options);
         if (value >= 0) {
-          if ((t->global.next_stream_id & 1) != (value & 1)) {
+          if ((t->next_stream_id & 1) != (value & 1)) {
             gpr_log(GPR_ERROR, "%s: low bit must be %d on %s",
                     GRPC_ARG_HTTP2_INITIAL_SEQUENCE_NUMBER,
-                    t->global.next_stream_id & 1,
-                    is_client ? "client" : "server");
+                    t->next_stream_id & 1, is_client ? "client" : "server");
           } else {
-            t->global.next_stream_id = (uint32_t)value;
+            t->next_stream_id = (uint32_t)value;
           }
         }
       } else if (0 == strcmp(channel_args->args[i].key,
@@ -366,16 +304,7 @@
         const int value =
             grpc_channel_arg_get_integer(&channel_args->args[i], options);
         if (value >= 0) {
-          t->global.stream_lookahead = (uint32_t)value;
-        }
-      } else if (0 == strcmp(channel_args->args[i].key,
-                             GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_DECODER)) {
-        const grpc_integer_options options = {-1, 0, INT_MAX};
-        const int value =
-            grpc_channel_arg_get_integer(&channel_args->args[i], options);
-        if (value >= 0) {
-          push_setting(exec_ctx, t, GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE,
-                       (uint32_t)value);
+          t->stream_lookahead = (uint32_t)value;
         }
       } else if (0 == strcmp(channel_args->args[i].key,
                              GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_ENCODER)) {
@@ -383,118 +312,125 @@
         const int value =
             grpc_channel_arg_get_integer(&channel_args->args[i], options);
         if (value >= 0) {
-          grpc_chttp2_hpack_compressor_set_max_usable_size(
-              &t->writing.hpack_compressor, (uint32_t)value);
+          grpc_chttp2_hpack_compressor_set_max_usable_size(&t->hpack_compressor,
+                                                           (uint32_t)value);
         }
-      } else if (0 == strcmp(channel_args->args[i].key,
-                             GRPC_ARG_MAX_METADATA_SIZE)) {
-        const grpc_integer_options options = {-1, 0, INT_MAX};
-        const int value =
-            grpc_channel_arg_get_integer(&channel_args->args[i], options);
-        if (value >= 0) {
-          push_setting(exec_ctx, t, GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,
-                       (uint32_t)value);
-        }
-      } else if (0 == strcmp(channel_args->args[i].key,
-                             GRPC_ARG_HTTP2_MAX_FRAME_SIZE)) {
-        if (channel_args->args[i].type != GRPC_ARG_INTEGER) {
-          gpr_log(GPR_ERROR, "%s: must be an integer",
-                  GRPC_ARG_HTTP2_MAX_FRAME_SIZE);
-        } else if (channel_args->args[i].value.integer < 16384 ||
-                   channel_args->args[i].value.integer > 16777215) {
-          gpr_log(GPR_ERROR, "%s: must be between 16384 and 16777215",
-                  GRPC_ARG_HTTP2_MAX_FRAME_SIZE);
-        } else {
-          push_setting(exec_ctx, t, GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE,
-                       (uint32_t)channel_args->args[i].value.integer);
+      } else {
+        static const struct {
+          const char *channel_arg_name;
+          grpc_chttp2_setting_id setting_id;
+          grpc_integer_options integer_options;
+          bool availability[2] /* server, client */;
+        } settings_map[] = {
+            {GRPC_ARG_MAX_CONCURRENT_STREAMS,
+             GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
+             {-1, 0, INT_MAX},
+             {true, false}},
+            {GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_DECODER,
+             GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE,
+             {-1, 0, INT_MAX},
+             {true, true}},
+            {GRPC_ARG_MAX_METADATA_SIZE,
+             GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,
+             {-1, 0, INT_MAX},
+             {true, true}},
+            {GRPC_ARG_HTTP2_MAX_FRAME_SIZE,
+             GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE,
+             {-1, 16384, 16777215},
+             {true, true}},
+        };
+        for (j = 0; j < (int)GPR_ARRAY_SIZE(settings_map); j++) {
+          if (0 == strcmp(channel_args->args[i].key,
+                          settings_map[j].channel_arg_name)) {
+            if (!settings_map[j].availability[is_client]) {
+              gpr_log(GPR_DEBUG, "%s is not available on %s",
+                      settings_map[j].channel_arg_name,
+                      is_client ? "clients" : "servers");
+            } else {
+              int value = grpc_channel_arg_get_integer(
+                  &channel_args->args[i], settings_map[j].integer_options);
+              if (value >= 0) {
+                push_setting(exec_ctx, t, settings_map[j].setting_id,
+                             (uint32_t)value);
+              }
+            }
+            break;
+          }
         }
       }
     }
   }
 
-  set_write_state(t, GRPC_CHTTP2_WRITING_INACTIVE, "uncork");
-  grpc_chttp2_initiate_write(exec_ctx, &t->global, false, "init");
+  grpc_chttp2_initiate_write(exec_ctx, t, false, "init");
 }
 
 static void destroy_transport_locked(grpc_exec_ctx *exec_ctx, void *tp,
                                      grpc_error *error) {
   grpc_chttp2_transport *t = tp;
   t->destroying = 1;
-  drop_connection(exec_ctx, t, GRPC_ERROR_CREATE("Transport destroyed"));
-  UNREF_TRANSPORT(exec_ctx, t, "destroy");
+  close_transport_locked(
+      exec_ctx, t,
+      grpc_error_set_int(GRPC_ERROR_CREATE("Transport destroyed"),
+                         GRPC_ERROR_INT_OCCURRED_DURING_WRITE, t->write_state));
+  GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "destroy");
 }
 
 static void destroy_transport(grpc_exec_ctx *exec_ctx, grpc_transport *gt) {
   grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt;
-  grpc_combiner_execute(exec_ctx, t->executor.combiner,
+  grpc_combiner_execute(exec_ctx, t->combiner,
                         grpc_closure_create(destroy_transport_locked, t),
-                        GRPC_ERROR_NONE);
-}
-
-/** block grpc_endpoint_shutdown being called until a paired
-    allow_endpoint_shutdown is made */
-static void prevent_endpoint_shutdown(grpc_chttp2_transport *t) {
-  gpr_ref(&t->shutdown_ep_refs);
-}
-
-static void allow_endpoint_shutdown_locked(grpc_exec_ctx *exec_ctx,
-                                           grpc_chttp2_transport *t) {
-  if (gpr_unref(&t->shutdown_ep_refs)) {
-    grpc_endpoint_shutdown(exec_ctx, t->ep);
-  }
+                        GRPC_ERROR_NONE, false);
 }
 
 static void close_transport_locked(grpc_exec_ctx *exec_ctx,
                                    grpc_chttp2_transport *t,
                                    grpc_error *error) {
   if (!t->closed) {
-    if (grpc_http_write_state_trace) {
-      gpr_log(GPR_DEBUG, "W:%p close transport", t);
+    if (t->write_state != GRPC_CHTTP2_WRITE_STATE_IDLE) {
+      if (t->close_transport_on_writes_finished == NULL) {
+        t->close_transport_on_writes_finished =
+            GRPC_ERROR_CREATE("Delayed close due to in-progress write");
+      }
+      t->close_transport_on_writes_finished =
+          grpc_error_add_child(t->close_transport_on_writes_finished, error);
+      return;
+    }
+    if (!grpc_error_get_int(error, GRPC_ERROR_INT_GRPC_STATUS, NULL)) {
+      error = grpc_error_set_int(error, GRPC_ERROR_INT_GRPC_STATUS,
+                                 GRPC_STATUS_UNAVAILABLE);
     }
     t->closed = 1;
-    connectivity_state_set(exec_ctx, &t->global, GRPC_CHANNEL_SHUTDOWN,
+    connectivity_state_set(exec_ctx, t, GRPC_CHANNEL_SHUTDOWN,
                            GRPC_ERROR_REF(error), "close_transport");
-    allow_endpoint_shutdown_locked(exec_ctx, t);
+    grpc_endpoint_shutdown(exec_ctx, t->ep);
 
     /* flush writable stream list to avoid dangling references */
-    grpc_chttp2_stream_global *stream_global;
-    grpc_chttp2_stream_writing *stream_writing;
-    while (grpc_chttp2_list_pop_writable_stream(
-        &t->global, &t->writing, &stream_global, &stream_writing)) {
-      GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2_writing");
+    grpc_chttp2_stream *s;
+    while (grpc_chttp2_list_pop_writable_stream(t, &s)) {
+      GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2_writing:close");
     }
+    end_all_the_calls(exec_ctx, t, GRPC_ERROR_REF(error));
   }
   GRPC_ERROR_UNREF(error);
 }
 
 #ifdef GRPC_STREAM_REFCOUNT_DEBUG
-void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global,
-                            const char *reason) {
-  grpc_stream_ref(STREAM_FROM_GLOBAL(stream_global)->refcount, reason);
+void grpc_chttp2_stream_ref(grpc_chttp2_stream *s, const char *reason) {
+  grpc_stream_ref(s->refcount, reason);
 }
-void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx,
-                              grpc_chttp2_stream_global *stream_global,
+void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, grpc_chttp2_stream *s,
                               const char *reason) {
-  grpc_stream_unref(exec_ctx, STREAM_FROM_GLOBAL(stream_global)->refcount,
-                    reason);
+  grpc_stream_unref(exec_ctx, s->refcount, reason);
 }
 #else
-void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global) {
-  grpc_stream_ref(STREAM_FROM_GLOBAL(stream_global)->refcount);
+void grpc_chttp2_stream_ref(grpc_chttp2_stream *s) {
+  grpc_stream_ref(s->refcount);
 }
-void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx,
-                              grpc_chttp2_stream_global *stream_global) {
-  grpc_stream_unref(exec_ctx, STREAM_FROM_GLOBAL(stream_global)->refcount);
+void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, grpc_chttp2_stream *s) {
+  grpc_stream_unref(exec_ctx, s->refcount);
 }
 #endif
 
-static void finish_init_stream_locked(grpc_exec_ctx *exec_ctx, void *sp,
-                                      grpc_error *error) {
-  grpc_chttp2_stream *s = sp;
-  grpc_chttp2_register_stream(s->t, s);
-  GRPC_CHTTP2_STREAM_UNREF(exec_ctx, &s->global, "init");
-}
-
 static int init_stream(grpc_exec_ctx *exec_ctx, grpc_transport *gt,
                        grpc_stream *gs, grpc_stream_refcount *refcount,
                        const void *server_data) {
@@ -509,41 +445,30 @@
   /* We reserve one 'active stream' that's dropped when the stream is
      read-closed. The others are for incoming_byte_streams that are actively
      reading */
-  gpr_ref_init(&s->global.active_streams, 1);
-  GRPC_CHTTP2_STREAM_REF(&s->global, "chttp2");
+  gpr_ref_init(&s->active_streams, 1);
+  GRPC_CHTTP2_STREAM_REF(s, "chttp2");
 
-  grpc_chttp2_incoming_metadata_buffer_init(&s->parsing.metadata_buffer[0]);
-  grpc_chttp2_incoming_metadata_buffer_init(&s->parsing.metadata_buffer[1]);
-  grpc_chttp2_incoming_metadata_buffer_init(
-      &s->global.received_initial_metadata);
-  grpc_chttp2_incoming_metadata_buffer_init(
-      &s->global.received_trailing_metadata);
-  grpc_chttp2_data_parser_init(&s->parsing.data_parser);
-  gpr_slice_buffer_init(&s->writing.flow_controlled_buffer);
-  s->global.deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
+  grpc_chttp2_incoming_metadata_buffer_init(&s->metadata_buffer[0]);
+  grpc_chttp2_incoming_metadata_buffer_init(&s->metadata_buffer[1]);
+  grpc_chttp2_data_parser_init(&s->data_parser);
+  gpr_slice_buffer_init(&s->flow_controlled_buffer);
+  s->deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
+  grpc_closure_init(&s->complete_fetch, complete_fetch, s);
+  grpc_closure_init(&s->complete_fetch_locked, complete_fetch_locked, s);
 
-  REF_TRANSPORT(t, "stream");
+  GRPC_CHTTP2_REF_TRANSPORT(t, "stream");
 
   if (server_data) {
-    GPR_ASSERT(t->executor.parsing_active);
-    s->global.id = (uint32_t)(uintptr_t)server_data;
-    s->parsing.id = s->global.id;
-    s->global.outgoing_window =
-        t->global.settings[GRPC_PEER_SETTINGS]
-                          [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
-    s->parsing.incoming_window = s->global.max_recv_bytes =
-        t->global.settings[GRPC_SENT_SETTINGS]
-                          [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
+    s->id = (uint32_t)(uintptr_t)server_data;
+    s->outgoing_window = t->settings[GRPC_PEER_SETTINGS]
+                                    [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
+    s->incoming_window = s->max_recv_bytes =
+        t->settings[GRPC_SENT_SETTINGS]
+                   [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
     *t->accepting_stream = s;
-    grpc_chttp2_stream_map_add(&t->parsing_stream_map, s->global.id, s);
-    s->global.in_stream_map = true;
+    grpc_chttp2_stream_map_add(&t->stream_map, s->id, s);
   }
 
-  grpc_closure_init(&s->init_stream, finish_init_stream_locked, s);
-  GRPC_CHTTP2_STREAM_REF(&s->global, "init");
-  grpc_combiner_execute(exec_ctx, t->executor.combiner, &s->init_stream,
-                        GRPC_ERROR_NONE);
-
   GPR_TIMER_END("init_stream", 0);
 
   return 0;
@@ -557,55 +482,39 @@
 
   GPR_TIMER_BEGIN("destroy_stream", 0);
 
-  GPR_ASSERT((s->global.write_closed && s->global.read_closed) ||
-             s->global.id == 0);
-  GPR_ASSERT(!s->global.in_stream_map);
-  if (grpc_chttp2_unregister_stream(t, s) && t->global.sent_goaway) {
-    close_transport_locked(
-        exec_ctx, t,
-        GRPC_ERROR_CREATE("Last stream closed after sending goaway"));
-  }
-  if (!t->executor.parsing_active && s->global.id) {
-    GPR_ASSERT(grpc_chttp2_stream_map_find(&t->parsing_stream_map,
-                                           s->global.id) == NULL);
+  GPR_ASSERT((s->write_closed && s->read_closed) || s->id == 0);
+  if (s->id != 0) {
+    GPR_ASSERT(grpc_chttp2_stream_map_find(&t->stream_map, s->id) == NULL);
   }
 
-  while (
-      (bs = grpc_chttp2_incoming_frame_queue_pop(&s->global.incoming_frames))) {
+  while ((bs = grpc_chttp2_incoming_frame_queue_pop(&s->incoming_frames))) {
     incoming_byte_stream_destroy_locked(exec_ctx, bs, GRPC_ERROR_NONE);
   }
 
-  grpc_chttp2_list_remove_unannounced_incoming_window_available(&t->global,
-                                                                &s->global);
-  grpc_chttp2_list_remove_stalled_by_transport(&t->global, &s->global);
-  grpc_chttp2_list_remove_check_read_ops(&t->global, &s->global);
+  grpc_chttp2_list_remove_stalled_by_transport(t, s);
 
   for (int i = 0; i < STREAM_LIST_COUNT; i++) {
     if (s->included[i]) {
       gpr_log(GPR_ERROR, "%s stream %d still included in list %d",
-              t->global.is_client ? "client" : "server", s->global.id, i);
+              t->is_client ? "client" : "server", s->id, i);
       abort();
     }
   }
 
-  GPR_ASSERT(s->global.send_initial_metadata_finished == NULL);
-  GPR_ASSERT(s->global.send_message_finished == NULL);
-  GPR_ASSERT(s->global.send_trailing_metadata_finished == NULL);
-  GPR_ASSERT(s->global.recv_initial_metadata_ready == NULL);
-  GPR_ASSERT(s->global.recv_message_ready == NULL);
-  GPR_ASSERT(s->global.recv_trailing_metadata_finished == NULL);
-  grpc_chttp2_data_parser_destroy(exec_ctx, &s->parsing.data_parser);
-  grpc_chttp2_incoming_metadata_buffer_destroy(&s->parsing.metadata_buffer[0]);
-  grpc_chttp2_incoming_metadata_buffer_destroy(&s->parsing.metadata_buffer[1]);
-  grpc_chttp2_incoming_metadata_buffer_destroy(
-      &s->global.received_initial_metadata);
-  grpc_chttp2_incoming_metadata_buffer_destroy(
-      &s->global.received_trailing_metadata);
-  gpr_slice_buffer_destroy(&s->writing.flow_controlled_buffer);
-  GRPC_ERROR_UNREF(s->global.read_closed_error);
-  GRPC_ERROR_UNREF(s->global.write_closed_error);
+  GPR_ASSERT(s->send_initial_metadata_finished == NULL);
+  GPR_ASSERT(s->fetching_send_message == NULL);
+  GPR_ASSERT(s->send_trailing_metadata_finished == NULL);
+  GPR_ASSERT(s->recv_initial_metadata_ready == NULL);
+  GPR_ASSERT(s->recv_message_ready == NULL);
+  GPR_ASSERT(s->recv_trailing_metadata_finished == NULL);
+  grpc_chttp2_data_parser_destroy(exec_ctx, &s->data_parser);
+  grpc_chttp2_incoming_metadata_buffer_destroy(&s->metadata_buffer[0]);
+  grpc_chttp2_incoming_metadata_buffer_destroy(&s->metadata_buffer[1]);
+  gpr_slice_buffer_destroy(&s->flow_controlled_buffer);
+  GRPC_ERROR_UNREF(s->read_closed_error);
+  GRPC_ERROR_UNREF(s->write_closed_error);
 
-  UNREF_TRANSPORT(exec_ctx, t, "stream");
+  GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "stream");
 
   GPR_TIMER_END("destroy_stream", 0);
 
@@ -620,174 +529,184 @@
 
   s->destroy_stream_arg = and_free_memory;
   grpc_closure_init(&s->destroy_stream, destroy_stream_locked, s);
-  grpc_combiner_execute(exec_ctx, t->executor.combiner, &s->destroy_stream,
-                        GRPC_ERROR_NONE);
+  grpc_combiner_execute(exec_ctx, t->combiner, &s->destroy_stream,
+                        GRPC_ERROR_NONE, false);
   GPR_TIMER_END("destroy_stream", 0);
 }
 
-grpc_chttp2_stream_parsing *grpc_chttp2_parsing_lookup_stream(
-    grpc_chttp2_transport_parsing *transport_parsing, uint32_t id) {
-  grpc_chttp2_transport *t = TRANSPORT_FROM_PARSING(transport_parsing);
-  grpc_chttp2_stream *s =
-      grpc_chttp2_stream_map_find(&t->parsing_stream_map, id);
-  return s ? &s->parsing : NULL;
+grpc_chttp2_stream *grpc_chttp2_parsing_lookup_stream(grpc_chttp2_transport *t,
+                                                      uint32_t id) {
+  return grpc_chttp2_stream_map_find(&t->stream_map, id);
 }
 
-grpc_chttp2_stream_parsing *grpc_chttp2_parsing_accept_stream(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    uint32_t id) {
+grpc_chttp2_stream *grpc_chttp2_parsing_accept_stream(grpc_exec_ctx *exec_ctx,
+                                                      grpc_chttp2_transport *t,
+                                                      uint32_t id) {
+  if (t->channel_callback.accept_stream == NULL) {
+    return NULL;
+  }
   grpc_chttp2_stream *accepting;
-  grpc_chttp2_transport *t = TRANSPORT_FROM_PARSING(transport_parsing);
   GPR_ASSERT(t->accepting_stream == NULL);
   t->accepting_stream = &accepting;
   t->channel_callback.accept_stream(exec_ctx,
                                     t->channel_callback.accept_stream_user_data,
                                     &t->base, (void *)(uintptr_t)id);
   t->accepting_stream = NULL;
-  return &accepting->parsing;
-}
-
-/*******************************************************************************
- * LOCK MANAGEMENT
- */
-
-static const char *write_state_name(grpc_chttp2_write_state state) {
-  switch (state) {
-    case GRPC_CHTTP2_WRITES_CORKED:
-      return "CORKED";
-    case GRPC_CHTTP2_WRITING_INACTIVE:
-      return "INACTIVE";
-    case GRPC_CHTTP2_WRITE_SCHEDULED:
-      return "SCHEDULED";
-    case GRPC_CHTTP2_WRITING:
-      return "WRITING";
-    case GRPC_CHTTP2_WRITING_STALE_WITH_POLLER:
-      return "WRITING[p=1]";
-    case GRPC_CHTTP2_WRITING_STALE_NO_POLLER:
-      return "WRITING[p=0]";
-  }
-  GPR_UNREACHABLE_CODE(return "UNKNOWN");
-}
-
-static void set_write_state(grpc_chttp2_transport *t,
-                            grpc_chttp2_write_state state, const char *reason) {
-  if (grpc_http_write_state_trace) {
-    gpr_log(GPR_DEBUG, "W:%p %s -> %s because %s", t,
-            write_state_name(t->executor.write_state), write_state_name(state),
-            reason);
-  }
-  t->executor.write_state = state;
-}
-
-static void initiate_writing_locked(grpc_exec_ctx *exec_ctx, void *tp,
-                                    grpc_error *error) {
-  grpc_chttp2_transport *t = tp;
-  GPR_ASSERT(t->executor.write_state == GRPC_CHTTP2_WRITE_SCHEDULED);
-  start_writing(exec_ctx, t);
-}
-
-static void initiate_read_flush_locked(grpc_exec_ctx *exec_ctx, void *tp,
-                                       grpc_error *error) {
-  grpc_chttp2_transport *t = tp;
-  t->executor.check_read_ops_scheduled = false;
-  check_read_ops(exec_ctx, &t->global);
+  return accepting;
 }
 
 /*******************************************************************************
  * OUTPUT PROCESSING
  */
 
+static const char *write_state_name(grpc_chttp2_write_state st) {
+  switch (st) {
+    case GRPC_CHTTP2_WRITE_STATE_IDLE:
+      return "IDLE";
+    case GRPC_CHTTP2_WRITE_STATE_WRITING:
+      return "WRITING";
+    case GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE:
+      return "WRITING+MORE";
+    case GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE_AND_COVERED_BY_POLLER:
+      return "WRITING+MORE+COVERED";
+  }
+  GPR_UNREACHABLE_CODE(return "UNKNOWN");
+}
+
+static void set_write_state(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                            grpc_chttp2_write_state st, const char *reason) {
+  GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_DEBUG, "W:%p %s state %s -> %s [%s]", t,
+                                 t->is_client ? "CLIENT" : "SERVER",
+                                 write_state_name(t->write_state),
+                                 write_state_name(st), reason));
+  t->write_state = st;
+  if (st == GRPC_CHTTP2_WRITE_STATE_IDLE &&
+      t->close_transport_on_writes_finished != NULL) {
+    grpc_error *err = t->close_transport_on_writes_finished;
+    t->close_transport_on_writes_finished = NULL;
+    close_transport_locked(exec_ctx, t, err);
+  }
+}
+
 void grpc_chttp2_initiate_write(grpc_exec_ctx *exec_ctx,
-                                grpc_chttp2_transport_global *transport_global,
+                                grpc_chttp2_transport *t,
                                 bool covered_by_poller, const char *reason) {
   GPR_TIMER_BEGIN("grpc_chttp2_initiate_write", 0);
 
-  /* Perform state checks, and transition to a scheduled state if appropriate.
-     If we are inactive, schedule a write chain to begin once the transport
-     combiner finishes any executions in its current batch (which may be
-     scheduled AFTER this code executes). The write chain will:
-      - call start_writing, which verifies (under the global lock) that there
-        are things that need to be written by calling
-        grpc_chttp2_unlocking_check_writes, and if so schedules writing_action
-        against the current exec_ctx, to be executed OUTSIDE of the global lock
-      - eventually writing_action results in grpc_chttp2_terminate_writing being
-        called, which re-takes the global lock, updates state, checks if we need
-        to do *another* write immediately, and if so loops back to
-        start_writing.
-
-     Current problems:
-       - too much lock entry/exiting
-       - the writing thread can become stuck indefinitely (punt through the
-         workqueue periodically to fix) */
-
-  grpc_chttp2_transport *t = TRANSPORT_FROM_GLOBAL(transport_global);
-  switch (t->executor.write_state) {
-    case GRPC_CHTTP2_WRITES_CORKED:
+  switch (t->write_state) {
+    case GRPC_CHTTP2_WRITE_STATE_IDLE:
+      set_write_state(exec_ctx, t, GRPC_CHTTP2_WRITE_STATE_WRITING, reason);
+      GRPC_CHTTP2_REF_TRANSPORT(t, "writing");
+      grpc_combiner_execute_finally(exec_ctx, t->combiner,
+                                    &t->write_action_begin_locked,
+                                    GRPC_ERROR_NONE, covered_by_poller);
       break;
-    case GRPC_CHTTP2_WRITING_INACTIVE:
-      set_write_state(t, GRPC_CHTTP2_WRITE_SCHEDULED, reason);
-      REF_TRANSPORT(t, "writing");
-      grpc_combiner_execute_finally(exec_ctx, t->executor.combiner,
-                                    &t->initiate_writing, GRPC_ERROR_NONE,
-                                    covered_by_poller);
+    case GRPC_CHTTP2_WRITE_STATE_WRITING:
+      set_write_state(
+          exec_ctx, t,
+          covered_by_poller
+              ? GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE_AND_COVERED_BY_POLLER
+              : GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE,
+          reason);
       break;
-    case GRPC_CHTTP2_WRITE_SCHEDULED:
+    case GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE:
       if (covered_by_poller) {
-        /* upgrade to note poller is available to cover the write */
-        grpc_combiner_force_async_finally(t->executor.combiner);
+        set_write_state(
+            exec_ctx, t,
+            GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE_AND_COVERED_BY_POLLER,
+            reason);
       }
       break;
-    case GRPC_CHTTP2_WRITING:
-      set_write_state(t,
-                      covered_by_poller ? GRPC_CHTTP2_WRITING_STALE_WITH_POLLER
-                                        : GRPC_CHTTP2_WRITING_STALE_NO_POLLER,
-                      reason);
-      break;
-    case GRPC_CHTTP2_WRITING_STALE_WITH_POLLER:
-      /* nothing to do: write already requested */
-      break;
-    case GRPC_CHTTP2_WRITING_STALE_NO_POLLER:
-      if (covered_by_poller) {
-        /* upgrade to note poller is available to cover the write */
-        set_write_state(t, GRPC_CHTTP2_WRITING_STALE_WITH_POLLER, reason);
-      }
+    case GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE_AND_COVERED_BY_POLLER:
       break;
   }
   GPR_TIMER_END("grpc_chttp2_initiate_write", 0);
 }
 
-static void start_writing(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) {
-  GPR_TIMER_BEGIN("start_writing", 0);
-  GPR_ASSERT(t->executor.write_state == GRPC_CHTTP2_WRITE_SCHEDULED);
-  if (!t->closed &&
-      grpc_chttp2_unlocking_check_writes(exec_ctx, &t->global, &t->writing)) {
-    set_write_state(t, GRPC_CHTTP2_WRITING, "start_writing");
-    prevent_endpoint_shutdown(t);
-    grpc_exec_ctx_sched(exec_ctx, &t->writing_action, GRPC_ERROR_NONE, NULL);
-  } else {
-    if (t->closed) {
-      set_write_state(t, GRPC_CHTTP2_WRITING_INACTIVE,
-                      "start_writing:transport_closed");
-    } else {
-      set_write_state(t, GRPC_CHTTP2_WRITING_INACTIVE,
-                      "start_writing:nothing_to_write");
-    }
-    end_waiting_for_write(exec_ctx, t, GRPC_ERROR_NONE);
-    UNREF_TRANSPORT(exec_ctx, t, "writing");
+void grpc_chttp2_become_writable(grpc_exec_ctx *exec_ctx,
+                                 grpc_chttp2_transport *t,
+                                 grpc_chttp2_stream *s, bool covered_by_poller,
+                                 const char *reason) {
+  if (!t->closed && grpc_chttp2_list_add_writable_stream(t, s)) {
+    GRPC_CHTTP2_STREAM_REF(s, "chttp2_writing:become");
+    grpc_chttp2_initiate_write(exec_ctx, t, covered_by_poller, reason);
   }
-  GPR_TIMER_END("start_writing", 0);
 }
 
-void grpc_chttp2_become_writable(grpc_exec_ctx *exec_ctx,
-                                 grpc_chttp2_transport_global *transport_global,
-                                 grpc_chttp2_stream_global *stream_global,
-                                 bool covered_by_poller, const char *reason) {
-  if (!TRANSPORT_FROM_GLOBAL(transport_global)->closed &&
-      grpc_chttp2_list_add_writable_stream(transport_global, stream_global)) {
-    GRPC_CHTTP2_STREAM_REF(stream_global, "chttp2_writing");
-    grpc_chttp2_initiate_write(exec_ctx, transport_global, covered_by_poller,
-                               reason);
+static void write_action_begin_locked(grpc_exec_ctx *exec_ctx, void *gt,
+                                      grpc_error *error_ignored) {
+  GPR_TIMER_BEGIN("write_action_begin_locked", 0);
+  grpc_chttp2_transport *t = gt;
+  GPR_ASSERT(t->write_state != GRPC_CHTTP2_WRITE_STATE_IDLE);
+  if (!t->closed && grpc_chttp2_begin_write(exec_ctx, t)) {
+    set_write_state(exec_ctx, t, GRPC_CHTTP2_WRITE_STATE_WRITING,
+                    "begin writing");
+    grpc_exec_ctx_sched(exec_ctx, &t->write_action, GRPC_ERROR_NONE, NULL);
+  } else {
+    set_write_state(exec_ctx, t, GRPC_CHTTP2_WRITE_STATE_IDLE,
+                    "begin writing nothing");
+    GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "writing");
   }
+  GPR_TIMER_END("write_action_begin_locked", 0);
+}
+
+static void write_action(grpc_exec_ctx *exec_ctx, void *gt, grpc_error *error) {
+  grpc_chttp2_transport *t = gt;
+  GPR_TIMER_BEGIN("write_action", 0);
+  grpc_endpoint_write(exec_ctx, t->ep, &t->outbuf, &t->write_action_end);
+  GPR_TIMER_END("write_action", 0);
+}
+
+static void write_action_end(grpc_exec_ctx *exec_ctx, void *gt,
+                             grpc_error *error) {
+  grpc_chttp2_transport *t = gt;
+  GPR_TIMER_BEGIN("write_action_end", 0);
+  grpc_combiner_execute(exec_ctx, t->combiner, &t->write_action_end_locked,
+                        GRPC_ERROR_REF(error), false);
+  GPR_TIMER_END("write_action_end", 0);
+}
+
+static void write_action_end_locked(grpc_exec_ctx *exec_ctx, void *tp,
+                                    grpc_error *error) {
+  GPR_TIMER_BEGIN("terminate_writing_with_lock", 0);
+  grpc_chttp2_transport *t = tp;
+
+  if (error != GRPC_ERROR_NONE) {
+    close_transport_locked(exec_ctx, t, GRPC_ERROR_REF(error));
+  }
+
+  grpc_chttp2_end_write(exec_ctx, t, GRPC_ERROR_REF(error));
+
+  switch (t->write_state) {
+    case GRPC_CHTTP2_WRITE_STATE_IDLE:
+      GPR_UNREACHABLE_CODE(break);
+    case GRPC_CHTTP2_WRITE_STATE_WRITING:
+      GPR_TIMER_MARK("state=writing", 0);
+      set_write_state(exec_ctx, t, GRPC_CHTTP2_WRITE_STATE_IDLE,
+                      "finish writing");
+      break;
+    case GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE:
+      GPR_TIMER_MARK("state=writing_stale_no_poller", 0);
+      set_write_state(exec_ctx, t, GRPC_CHTTP2_WRITE_STATE_WRITING,
+                      "continue writing [!covered]");
+      GRPC_CHTTP2_REF_TRANSPORT(t, "writing");
+      grpc_combiner_execute_finally(exec_ctx, t->combiner,
+                                    &t->write_action_begin_locked,
+                                    GRPC_ERROR_NONE, false);
+      break;
+    case GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE_AND_COVERED_BY_POLLER:
+      GPR_TIMER_MARK("state=writing_stale_with_poller", 0);
+      set_write_state(exec_ctx, t, GRPC_CHTTP2_WRITE_STATE_WRITING,
+                      "continue writing [covered]");
+      GRPC_CHTTP2_REF_TRANSPORT(t, "writing");
+      grpc_combiner_execute_finally(exec_ctx, t->combiner,
+                                    &t->write_action_begin_locked,
+                                    GRPC_ERROR_NONE, true);
+      break;
+  }
+
+  GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "writing");
+  GPR_TIMER_END("terminate_writing_with_lock", 0);
 }
 
 static void push_setting(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
@@ -799,101 +718,26 @@
     gpr_log(GPR_INFO, "Requested parameter %s clamped from %d to %d", sp->name,
             value, use_value);
   }
-  if (use_value != t->global.settings[GRPC_LOCAL_SETTINGS][id]) {
-    t->global.settings[GRPC_LOCAL_SETTINGS][id] = use_value;
-    t->global.dirtied_local_settings = 1;
-    grpc_chttp2_initiate_write(exec_ctx, &t->global, false, "push_setting");
+  if (use_value != t->settings[GRPC_LOCAL_SETTINGS][id]) {
+    t->settings[GRPC_LOCAL_SETTINGS][id] = use_value;
+    t->dirtied_local_settings = 1;
+    grpc_chttp2_initiate_write(exec_ctx, t, false, "push_setting");
   }
 }
 
-/* error may be GRPC_ERROR_NONE if there is no error allocated yet.
-   In that case, use "reason" as the text for a new error. */
-static void end_waiting_for_write(grpc_exec_ctx *exec_ctx,
-                                  grpc_chttp2_transport *t, grpc_error *error) {
-  grpc_chttp2_stream_global *stream_global;
-  while (grpc_chttp2_list_pop_closed_waiting_for_writing(&t->global,
-                                                         &stream_global)) {
-    fail_pending_writes(exec_ctx, &t->global, stream_global,
-                        GRPC_ERROR_REF(error));
-    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "finish_writes");
-  }
-  GRPC_ERROR_UNREF(error);
-}
-
-static void terminate_writing_with_lock(grpc_exec_ctx *exec_ctx, void *tp,
-                                        grpc_error *error) {
-  GPR_TIMER_BEGIN("terminate_writing_with_lock", 0);
-  grpc_chttp2_transport *t = tp;
-  allow_endpoint_shutdown_locked(exec_ctx, t);
-
-  if (error != GRPC_ERROR_NONE) {
-    drop_connection(exec_ctx, t, GRPC_ERROR_REF(error));
-  }
-
-  grpc_chttp2_cleanup_writing(exec_ctx, &t->global, &t->writing);
-
-  end_waiting_for_write(exec_ctx, t, GRPC_ERROR_REF(error));
-
-  switch (t->executor.write_state) {
-    case GRPC_CHTTP2_WRITES_CORKED:
-    case GRPC_CHTTP2_WRITING_INACTIVE:
-    case GRPC_CHTTP2_WRITE_SCHEDULED:
-      GPR_UNREACHABLE_CODE(break);
-    case GRPC_CHTTP2_WRITING:
-      GPR_TIMER_MARK("state=writing", 0);
-      set_write_state(t, GRPC_CHTTP2_WRITING_INACTIVE, "terminate_writing");
-      break;
-    case GRPC_CHTTP2_WRITING_STALE_WITH_POLLER:
-      GPR_TIMER_MARK("state=writing_stale_with_poller", 0);
-      set_write_state(t, GRPC_CHTTP2_WRITE_SCHEDULED, "terminate_writing");
-      REF_TRANSPORT(t, "writing");
-      grpc_combiner_execute_finally(exec_ctx, t->executor.combiner,
-                                    &t->initiate_writing, GRPC_ERROR_NONE,
-                                    true);
-      break;
-    case GRPC_CHTTP2_WRITING_STALE_NO_POLLER:
-      GPR_TIMER_MARK("state=writing_stale_no_poller", 0);
-      set_write_state(t, GRPC_CHTTP2_WRITE_SCHEDULED, "terminate_writing");
-      REF_TRANSPORT(t, "writing");
-      grpc_combiner_execute_finally(exec_ctx, t->executor.combiner,
-                                    &t->initiate_writing, GRPC_ERROR_NONE,
-                                    false);
-      break;
-  }
-
-  UNREF_TRANSPORT(exec_ctx, t, "writing");
-  GPR_TIMER_END("terminate_writing_with_lock", 0);
-}
-
-void grpc_chttp2_terminate_writing(grpc_exec_ctx *exec_ctx,
-                                   void *transport_writing, grpc_error *error) {
-  GPR_TIMER_BEGIN("grpc_chttp2_terminate_writing", 0);
-  grpc_chttp2_transport *t = TRANSPORT_FROM_WRITING(transport_writing);
-  grpc_combiner_execute(exec_ctx, t->executor.combiner, &t->terminate_writing,
-                        GRPC_ERROR_REF(error));
-  GPR_TIMER_END("grpc_chttp2_terminate_writing", 0);
-}
-
-static void writing_action(grpc_exec_ctx *exec_ctx, void *gt,
-                           grpc_error *error) {
-  grpc_chttp2_transport *t = gt;
-  GPR_TIMER_BEGIN("writing_action", 0);
-  grpc_chttp2_perform_writes(exec_ctx, &t->writing, t->ep);
-  GPR_TIMER_END("writing_action", 0);
-}
-
-void grpc_chttp2_add_incoming_goaway(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    uint32_t goaway_error, gpr_slice goaway_text) {
+void grpc_chttp2_add_incoming_goaway(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t,
+                                     uint32_t goaway_error,
+                                     gpr_slice goaway_text) {
   char *msg = gpr_dump_slice(goaway_text, GPR_DUMP_HEX | GPR_DUMP_ASCII);
   GRPC_CHTTP2_IF_TRACING(
       gpr_log(GPR_DEBUG, "got goaway [%d]: %s", goaway_error, msg));
   gpr_slice_unref(goaway_text);
-  transport_global->seen_goaway = 1;
+  t->seen_goaway = 1;
   /* lie: use transient failure from the transport to indicate goaway has been
    * received */
   connectivity_state_set(
-      exec_ctx, transport_global, GRPC_CHANNEL_TRANSIENT_FAILURE,
+      exec_ctx, t, GRPC_CHANNEL_TRANSIENT_FAILURE,
       grpc_error_set_str(
           grpc_error_set_int(GRPC_ERROR_CREATE("GOAWAY received"),
                              GRPC_ERROR_INT_HTTP2_ERROR,
@@ -903,61 +747,49 @@
   gpr_free(msg);
 }
 
-static void maybe_start_some_streams(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global) {
-  grpc_chttp2_stream_global *stream_global;
+static void maybe_start_some_streams(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t) {
+  grpc_chttp2_stream *s;
   uint32_t stream_incoming_window;
   /* start streams where we have free grpc_chttp2_stream ids and free
    * concurrency */
-  while (transport_global->next_stream_id <= MAX_CLIENT_STREAM_ID &&
-         transport_global->concurrent_stream_count <
-             transport_global
-                 ->settings[GRPC_PEER_SETTINGS]
-                           [GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] &&
-         grpc_chttp2_list_pop_waiting_for_concurrency(transport_global,
-                                                      &stream_global)) {
+  while (t->next_stream_id <= MAX_CLIENT_STREAM_ID &&
+         grpc_chttp2_stream_map_size(&t->stream_map) <
+             t->settings[GRPC_PEER_SETTINGS]
+                        [GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] &&
+         grpc_chttp2_list_pop_waiting_for_concurrency(t, &s)) {
     /* safe since we can't (legally) be parsing this stream yet */
-    grpc_chttp2_stream_parsing *stream_parsing =
-        &STREAM_FROM_GLOBAL(stream_global)->parsing;
     GRPC_CHTTP2_IF_TRACING(gpr_log(
         GPR_DEBUG, "HTTP:%s: Allocating new grpc_chttp2_stream %p to id %d",
-        transport_global->is_client ? "CLI" : "SVR", stream_global,
-        transport_global->next_stream_id));
+        t->is_client ? "CLI" : "SVR", s, t->next_stream_id));
 
-    GPR_ASSERT(stream_global->id == 0);
-    stream_global->id = stream_parsing->id = transport_global->next_stream_id;
-    transport_global->next_stream_id += 2;
+    GPR_ASSERT(s->id == 0);
+    s->id = t->next_stream_id;
+    t->next_stream_id += 2;
 
-    if (transport_global->next_stream_id >= MAX_CLIENT_STREAM_ID) {
-      connectivity_state_set(
-          exec_ctx, transport_global, GRPC_CHANNEL_TRANSIENT_FAILURE,
-          GRPC_ERROR_CREATE("Stream IDs exhausted"), "no_more_stream_ids");
+    if (t->next_stream_id >= MAX_CLIENT_STREAM_ID) {
+      connectivity_state_set(exec_ctx, t, GRPC_CHANNEL_TRANSIENT_FAILURE,
+                             GRPC_ERROR_CREATE("Stream IDs exhausted"),
+                             "no_more_stream_ids");
     }
 
-    stream_global->outgoing_window =
-        transport_global->settings[GRPC_PEER_SETTINGS]
-                                  [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
-    stream_parsing->incoming_window = stream_incoming_window =
-        transport_global->settings[GRPC_SENT_SETTINGS]
-                                  [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
-    stream_global->max_recv_bytes =
-        GPR_MAX(stream_incoming_window, stream_global->max_recv_bytes);
-    grpc_chttp2_stream_map_add(
-        &TRANSPORT_FROM_GLOBAL(transport_global)->new_stream_map,
-        stream_global->id, STREAM_FROM_GLOBAL(stream_global));
-    stream_global->in_stream_map = true;
-    transport_global->concurrent_stream_count++;
-    grpc_chttp2_become_writable(exec_ctx, transport_global, stream_global, true,
-                                "new_stream");
+    s->outgoing_window = t->settings[GRPC_PEER_SETTINGS]
+                                    [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
+    s->incoming_window = stream_incoming_window =
+        t->settings[GRPC_SENT_SETTINGS]
+                   [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
+    s->max_recv_bytes = GPR_MAX(stream_incoming_window, s->max_recv_bytes);
+    grpc_chttp2_stream_map_add(&t->stream_map, s->id, s);
+    grpc_chttp2_become_writable(exec_ctx, t, s, true, "new_stream");
   }
   /* cancel out streams that will never be started */
-  while (transport_global->next_stream_id >= MAX_CLIENT_STREAM_ID &&
-         grpc_chttp2_list_pop_waiting_for_concurrency(transport_global,
-                                                      &stream_global)) {
-    cancel_from_api(exec_ctx, transport_global, stream_global,
-                    grpc_error_set_int(
-                        GRPC_ERROR_CREATE("Stream IDs exhausted"),
-                        GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE));
+  while (t->next_stream_id >= MAX_CLIENT_STREAM_ID &&
+         grpc_chttp2_list_pop_waiting_for_concurrency(t, &s)) {
+    grpc_chttp2_cancel_stream(
+        exec_ctx, t, s,
+        grpc_error_set_int(GRPC_ERROR_CREATE("Stream IDs exhausted"),
+                           GRPC_ERROR_INT_GRPC_STATUS,
+                           GRPC_STATUS_UNAVAILABLE));
   }
 }
 
@@ -969,48 +801,126 @@
   return closure;
 }
 
-void grpc_chttp2_complete_closure_step(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, grpc_closure **pclosure,
-    grpc_error *error) {
+static void null_then_run_closure(grpc_exec_ctx *exec_ctx,
+                                  grpc_closure **closure, grpc_error *error) {
+  grpc_closure *c = *closure;
+  *closure = NULL;
+  grpc_closure_run(exec_ctx, c, error);
+}
+
+void grpc_chttp2_complete_closure_step(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_transport *t,
+                                       grpc_chttp2_stream *s,
+                                       grpc_closure **pclosure,
+                                       grpc_error *error, const char *desc) {
   grpc_closure *closure = *pclosure;
+  *pclosure = NULL;
   if (closure == NULL) {
     GRPC_ERROR_UNREF(error);
     return;
   }
   closure->next_data.scratch -= CLOSURE_BARRIER_FIRST_REF_BIT;
   if (error != GRPC_ERROR_NONE) {
-    if (closure->error == GRPC_ERROR_NONE) {
-      closure->error =
+    if (closure->error_data.error == GRPC_ERROR_NONE) {
+      closure->error_data.error =
           GRPC_ERROR_CREATE("Error in HTTP transport completing operation");
-      closure->error = grpc_error_set_str(
-          closure->error, GRPC_ERROR_STR_TARGET_ADDRESS,
-          TRANSPORT_FROM_GLOBAL(transport_global)->peer_string);
+      closure->error_data.error =
+          grpc_error_set_str(closure->error_data.error,
+                             GRPC_ERROR_STR_TARGET_ADDRESS, t->peer_string);
     }
-    closure->error = grpc_error_add_child(closure->error, error);
+    closure->error_data.error =
+        grpc_error_add_child(closure->error_data.error, error);
   }
   if (closure->next_data.scratch < CLOSURE_BARRIER_FIRST_REF_BIT) {
     if (closure->next_data.scratch & CLOSURE_BARRIER_STATS_BIT) {
-      grpc_transport_move_stats(&stream_global->stats,
-                                stream_global->collecting_stats);
-      stream_global->collecting_stats = NULL;
+      grpc_transport_move_stats(&s->stats, s->collecting_stats);
+      s->collecting_stats = NULL;
     }
-    grpc_exec_ctx_sched(exec_ctx, closure, closure->error, NULL);
+    grpc_closure_run(exec_ctx, closure, closure->error_data.error);
   }
-  *pclosure = NULL;
 }
 
-static int contains_non_ok_status(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_metadata_batch *batch) {
+static bool contains_non_ok_status(grpc_metadata_batch *batch) {
   grpc_linked_mdelem *l;
   for (l = batch->list.head; l; l = l->next) {
     if (l->md->key == GRPC_MDSTR_GRPC_STATUS &&
         l->md != GRPC_MDELEM_GRPC_STATUS_0) {
-      return 1;
+      return true;
     }
   }
-  return 0;
+  return false;
+}
+
+static void add_fetched_slice_locked(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t,
+                                     grpc_chttp2_stream *s);
+
+static void continue_fetching_send_locked(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s) {
+  if (s->fetching_send_message == NULL) {
+    /* Stream was cancelled before message fetch completed */
+    abort(); /* TODO(ctiller): what cleanup here? */
+    return;
+  }
+  if (s->fetched_send_message_length == s->fetching_send_message->length) {
+    int64_t notify_offset = s->next_message_end_offset;
+    if (notify_offset <= s->flow_controlled_bytes_written) {
+      grpc_chttp2_complete_closure_step(
+          exec_ctx, t, s, &s->fetching_send_message_finished, GRPC_ERROR_NONE,
+          "fetching_send_message_finished");
+    } else {
+      grpc_chttp2_write_cb *cb = t->write_cb_pool;
+      if (cb == NULL) {
+        cb = gpr_malloc(sizeof(*cb));
+      } else {
+        t->write_cb_pool = cb->next;
+      }
+      cb->call_at_byte = notify_offset;
+      cb->closure = s->fetching_send_message_finished;
+      s->fetching_send_message_finished = NULL;
+      cb->next = s->on_write_finished_cbs;
+      s->on_write_finished_cbs = cb;
+    }
+    s->fetching_send_message = NULL;
+  } else if (grpc_byte_stream_next(exec_ctx, s->fetching_send_message,
+                                   &s->fetching_slice, UINT32_MAX,
+                                   &s->complete_fetch)) {
+    add_fetched_slice_locked(exec_ctx, t, s);
+  }
+}
+
+static void add_fetched_slice_locked(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t,
+                                     grpc_chttp2_stream *s) {
+  s->fetched_send_message_length +=
+      (uint32_t)GPR_SLICE_LENGTH(s->fetching_slice);
+  gpr_slice_buffer_add(&s->flow_controlled_buffer, s->fetching_slice);
+  if (s->id != 0) {
+    grpc_chttp2_become_writable(exec_ctx, t, s, true, "op.send_message");
+  }
+  continue_fetching_send_locked(exec_ctx, t, s);
+}
+
+static void complete_fetch_locked(grpc_exec_ctx *exec_ctx, void *gs,
+                                  grpc_error *error) {
+  grpc_chttp2_stream *s = gs;
+  grpc_chttp2_transport *t = s->t;
+  if (error == GRPC_ERROR_NONE) {
+    add_fetched_slice_locked(exec_ctx, t, s);
+  } else {
+    /* TODO(ctiller): what to do here */
+    abort();
+  }
+}
+
+static void complete_fetch(grpc_exec_ctx *exec_ctx, void *gs,
+                           grpc_error *error) {
+  grpc_chttp2_stream *s = gs;
+  grpc_chttp2_transport *t = s->t;
+  grpc_combiner_execute(exec_ctx, t->combiner, &s->complete_fetch_locked,
+                        GRPC_ERROR_REF(error),
+                        s->complete_fetch_covered_by_poller);
 }
 
 static void do_nothing(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {}
@@ -1022,12 +932,11 @@
   grpc_transport_stream_op *op = stream_op;
   grpc_chttp2_transport *t = op->transport_private.args[0];
   grpc_chttp2_stream *s = op->transport_private.args[1];
-  grpc_chttp2_transport_global *transport_global = &t->global;
-  grpc_chttp2_stream_global *stream_global = &s->global;
 
   if (grpc_http_trace) {
     char *str = grpc_transport_stream_op_string(op);
-    gpr_log(GPR_DEBUG, "perform_stream_op_locked: %s", str);
+    gpr_log(GPR_DEBUG, "perform_stream_op_locked: %s; on_complete = %p", str,
+            op->on_complete);
     gpr_free(str);
   }
 
@@ -1035,45 +944,42 @@
   if (on_complete == NULL) {
     on_complete = grpc_closure_create(do_nothing, NULL);
   }
+
   /* use final_data as a barrier until enqueue time; the inital counter is
      dropped at the end of this function */
   on_complete->next_data.scratch = CLOSURE_BARRIER_FIRST_REF_BIT;
-  on_complete->error = GRPC_ERROR_NONE;
+  on_complete->error_data.error = GRPC_ERROR_NONE;
 
   if (op->collect_stats != NULL) {
-    GPR_ASSERT(stream_global->collecting_stats == NULL);
-    stream_global->collecting_stats = op->collect_stats;
+    GPR_ASSERT(s->collecting_stats == NULL);
+    s->collecting_stats = op->collect_stats;
     on_complete->next_data.scratch |= CLOSURE_BARRIER_STATS_BIT;
   }
 
   if (op->cancel_error != GRPC_ERROR_NONE) {
-    cancel_from_api(exec_ctx, transport_global, stream_global,
-                    GRPC_ERROR_REF(op->cancel_error));
+    grpc_chttp2_cancel_stream(exec_ctx, t, s, GRPC_ERROR_REF(op->cancel_error));
   }
 
   if (op->close_error != GRPC_ERROR_NONE) {
-    close_from_api(exec_ctx, transport_global, stream_global,
-                   GRPC_ERROR_REF(op->close_error));
+    close_from_api(exec_ctx, t, s, GRPC_ERROR_REF(op->close_error));
   }
 
   if (op->send_initial_metadata != NULL) {
-    GPR_ASSERT(stream_global->send_initial_metadata_finished == NULL);
-    stream_global->send_initial_metadata_finished =
-        add_closure_barrier(on_complete);
-    stream_global->send_initial_metadata = op->send_initial_metadata;
+    GPR_ASSERT(s->send_initial_metadata_finished == NULL);
+    s->send_initial_metadata_finished = add_closure_barrier(on_complete);
+    s->send_initial_metadata = op->send_initial_metadata;
     const size_t metadata_size =
         grpc_metadata_batch_size(op->send_initial_metadata);
     const size_t metadata_peer_limit =
-        transport_global->settings[GRPC_PEER_SETTINGS]
-                                  [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
-    if (transport_global->is_client) {
-      stream_global->deadline =
-          gpr_time_min(stream_global->deadline,
-                       stream_global->send_initial_metadata->deadline);
+        t->settings[GRPC_PEER_SETTINGS]
+                   [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
+    if (t->is_client) {
+      s->deadline =
+          gpr_time_min(s->deadline, s->send_initial_metadata->deadline);
     }
     if (metadata_size > metadata_peer_limit) {
-      cancel_from_api(
-          exec_ctx, transport_global, stream_global,
+      grpc_chttp2_cancel_stream(
+          exec_ctx, t, s,
           grpc_error_set_int(
               grpc_error_set_int(
                   grpc_error_set_int(
@@ -1083,64 +989,78 @@
                   GRPC_ERROR_INT_LIMIT, (intptr_t)metadata_peer_limit),
               GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_RESOURCE_EXHAUSTED));
     } else {
-      if (contains_non_ok_status(transport_global, op->send_initial_metadata)) {
-        stream_global->seen_error = true;
-        grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                            stream_global);
+      if (contains_non_ok_status(op->send_initial_metadata)) {
+        s->seen_error = true;
       }
-      if (!stream_global->write_closed) {
-        if (transport_global->is_client) {
-          GPR_ASSERT(stream_global->id == 0);
-          grpc_chttp2_list_add_waiting_for_concurrency(transport_global,
-                                                       stream_global);
-          maybe_start_some_streams(exec_ctx, transport_global);
+      if (!s->write_closed) {
+        if (t->is_client) {
+          GPR_ASSERT(s->id == 0);
+          grpc_chttp2_list_add_waiting_for_concurrency(t, s);
+          maybe_start_some_streams(exec_ctx, t);
         } else {
-          GPR_ASSERT(stream_global->id != 0);
-          grpc_chttp2_become_writable(exec_ctx, transport_global, stream_global,
-                                      true, "op.send_initial_metadata");
+          GPR_ASSERT(s->id != 0);
+          grpc_chttp2_become_writable(exec_ctx, t, s, true,
+                                      "op.send_initial_metadata");
         }
       } else {
-        stream_global->send_trailing_metadata = NULL;
+        s->send_trailing_metadata = NULL;
         grpc_chttp2_complete_closure_step(
-            exec_ctx, transport_global, stream_global,
-            &stream_global->send_initial_metadata_finished,
+            exec_ctx, t, s, &s->send_initial_metadata_finished,
             GRPC_ERROR_CREATE(
-                "Attempt to send initial metadata after stream was closed"));
+                "Attempt to send initial metadata after stream was closed"),
+            "send_initial_metadata_finished");
       }
     }
   }
 
   if (op->send_message != NULL) {
-    GPR_ASSERT(stream_global->send_message_finished == NULL);
-    GPR_ASSERT(stream_global->send_message == NULL);
-    stream_global->send_message_finished = add_closure_barrier(on_complete);
-    if (stream_global->write_closed) {
+    s->fetching_send_message_finished = add_closure_barrier(op->on_complete);
+    if (s->write_closed) {
       grpc_chttp2_complete_closure_step(
-          exec_ctx, transport_global, stream_global,
-          &stream_global->send_message_finished,
-          GRPC_ERROR_CREATE("Attempt to send message after stream was closed"));
+          exec_ctx, t, s, &s->fetching_send_message_finished,
+          GRPC_ERROR_CREATE("Attempt to send message after stream was closed"),
+          "fetching_send_message_finished");
     } else {
-      stream_global->send_message = op->send_message;
-      if (stream_global->id != 0) {
-        grpc_chttp2_become_writable(exec_ctx, transport_global, stream_global,
-                                    true, "op.send_message");
+      GPR_ASSERT(s->fetching_send_message == NULL);
+      uint8_t *frame_hdr =
+          gpr_slice_buffer_tiny_add(&s->flow_controlled_buffer, 5);
+      uint32_t flags = op->send_message->flags;
+      frame_hdr[0] = (flags & GRPC_WRITE_INTERNAL_COMPRESS) != 0;
+      size_t len = op->send_message->length;
+      frame_hdr[1] = (uint8_t)(len >> 24);
+      frame_hdr[2] = (uint8_t)(len >> 16);
+      frame_hdr[3] = (uint8_t)(len >> 8);
+      frame_hdr[4] = (uint8_t)(len);
+      s->fetching_send_message = op->send_message;
+      s->fetched_send_message_length = 0;
+      s->next_message_end_offset = s->flow_controlled_bytes_written +
+                                   (int64_t)s->flow_controlled_buffer.length +
+                                   (int64_t)len;
+      s->complete_fetch_covered_by_poller = op->covered_by_poller;
+      if (flags & GRPC_WRITE_BUFFER_HINT) {
+        /* allow up to 64kb to be buffered */
+        /* TODO(ctiller): make this configurable */
+        s->next_message_end_offset -= 65536;
+      }
+      continue_fetching_send_locked(exec_ctx, t, s);
+      if (s->id != 0) {
+        grpc_chttp2_become_writable(exec_ctx, t, s, true, "op.send_message");
       }
     }
   }
 
   if (op->send_trailing_metadata != NULL) {
-    GPR_ASSERT(stream_global->send_trailing_metadata_finished == NULL);
-    stream_global->send_trailing_metadata_finished =
-        add_closure_barrier(on_complete);
-    stream_global->send_trailing_metadata = op->send_trailing_metadata;
+    GPR_ASSERT(s->send_trailing_metadata_finished == NULL);
+    s->send_trailing_metadata_finished = add_closure_barrier(on_complete);
+    s->send_trailing_metadata = op->send_trailing_metadata;
     const size_t metadata_size =
         grpc_metadata_batch_size(op->send_trailing_metadata);
     const size_t metadata_peer_limit =
-        transport_global->settings[GRPC_PEER_SETTINGS]
-                                  [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
+        t->settings[GRPC_PEER_SETTINGS]
+                   [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
     if (metadata_size > metadata_peer_limit) {
-      cancel_from_api(
-          exec_ctx, transport_global, stream_global,
+      grpc_chttp2_cancel_stream(
+          exec_ctx, t, s,
           grpc_error_set_int(
               grpc_error_set_int(
                   grpc_error_set_int(
@@ -1150,69 +1070,59 @@
                   GRPC_ERROR_INT_LIMIT, (intptr_t)metadata_peer_limit),
               GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_RESOURCE_EXHAUSTED));
     } else {
-      if (contains_non_ok_status(transport_global,
-                                 op->send_trailing_metadata)) {
-        stream_global->seen_error = true;
-        grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                            stream_global);
+      if (contains_non_ok_status(op->send_trailing_metadata)) {
+        s->seen_error = true;
       }
-      if (stream_global->write_closed) {
-        stream_global->send_trailing_metadata = NULL;
+      if (s->write_closed) {
+        s->send_trailing_metadata = NULL;
         grpc_chttp2_complete_closure_step(
-            exec_ctx, transport_global, stream_global,
-            &stream_global->send_trailing_metadata_finished,
+            exec_ctx, t, s, &s->send_trailing_metadata_finished,
             grpc_metadata_batch_is_empty(op->send_trailing_metadata)
                 ? GRPC_ERROR_NONE
                 : GRPC_ERROR_CREATE("Attempt to send trailing metadata after "
-                                    "stream was closed"));
-      } else if (stream_global->id != 0) {
+                                    "stream was closed"),
+            "send_trailing_metadata_finished");
+      } else if (s->id != 0) {
         /* TODO(ctiller): check if there's flow control for any outstanding
            bytes before going writable */
-        grpc_chttp2_become_writable(exec_ctx, transport_global, stream_global,
-                                    true, "op.send_trailing_metadata");
+        grpc_chttp2_become_writable(exec_ctx, t, s, true,
+                                    "op.send_trailing_metadata");
       }
     }
   }
 
   if (op->recv_initial_metadata != NULL) {
-    GPR_ASSERT(stream_global->recv_initial_metadata_ready == NULL);
-    stream_global->recv_initial_metadata_ready =
-        op->recv_initial_metadata_ready;
-    stream_global->recv_initial_metadata = op->recv_initial_metadata;
-    grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                        stream_global);
+    GPR_ASSERT(s->recv_initial_metadata_ready == NULL);
+    s->recv_initial_metadata_ready = op->recv_initial_metadata_ready;
+    s->recv_initial_metadata = op->recv_initial_metadata;
+    grpc_chttp2_maybe_complete_recv_initial_metadata(exec_ctx, t, s);
   }
 
   if (op->recv_message != NULL) {
-    GPR_ASSERT(stream_global->recv_message_ready == NULL);
-    stream_global->recv_message_ready = op->recv_message_ready;
-    stream_global->recv_message = op->recv_message;
-    if (stream_global->id != 0 &&
-        (stream_global->incoming_frames.head == NULL ||
-         stream_global->incoming_frames.head->is_tail)) {
-      incoming_byte_stream_update_flow_control(
-          exec_ctx, transport_global, stream_global,
-          transport_global->stream_lookahead, 0);
+    GPR_ASSERT(s->recv_message_ready == NULL);
+    s->recv_message_ready = op->recv_message_ready;
+    s->recv_message = op->recv_message;
+    if (s->id != 0 &&
+        (s->incoming_frames.head == NULL || s->incoming_frames.head->is_tail)) {
+      incoming_byte_stream_update_flow_control(exec_ctx, t, s,
+                                               t->stream_lookahead, 0);
     }
-    grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                        stream_global);
+    grpc_chttp2_maybe_complete_recv_message(exec_ctx, t, s);
   }
 
   if (op->recv_trailing_metadata != NULL) {
-    GPR_ASSERT(stream_global->recv_trailing_metadata_finished == NULL);
-    stream_global->recv_trailing_metadata_finished =
-        add_closure_barrier(on_complete);
-    stream_global->recv_trailing_metadata = op->recv_trailing_metadata;
-    stream_global->final_metadata_requested = true;
-    grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                        stream_global);
+    GPR_ASSERT(s->recv_trailing_metadata_finished == NULL);
+    s->recv_trailing_metadata_finished = add_closure_barrier(on_complete);
+    s->recv_trailing_metadata = op->recv_trailing_metadata;
+    s->final_metadata_requested = true;
+    grpc_chttp2_maybe_complete_recv_trailing_metadata(exec_ctx, t, s);
   }
 
-  grpc_chttp2_complete_closure_step(exec_ctx, transport_global, stream_global,
-                                    &on_complete, GRPC_ERROR_NONE);
+  grpc_chttp2_complete_closure_step(exec_ctx, t, s, &on_complete,
+                                    GRPC_ERROR_NONE, "op->on_complete");
 
   GPR_TIMER_END("perform_stream_op_locked", 0);
-  GRPC_CHTTP2_STREAM_UNREF(exec_ctx, &s->global, "perform_stream_op");
+  GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "perform_stream_op");
 }
 
 static void perform_stream_op(grpc_exec_ctx *exec_ctx, grpc_transport *gt,
@@ -1220,70 +1130,60 @@
   GPR_TIMER_BEGIN("perform_stream_op", 0);
   grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt;
   grpc_chttp2_stream *s = (grpc_chttp2_stream *)gs;
+
+  if (grpc_http_trace) {
+    char *str = grpc_transport_stream_op_string(op);
+    gpr_log(GPR_DEBUG, "perform_stream_op[s=%p/%d]: %s", s, s->id, str);
+    gpr_free(str);
+  }
+
   grpc_closure_init(&op->transport_private.closure, perform_stream_op_locked,
                     op);
   op->transport_private.args[0] = gt;
   op->transport_private.args[1] = gs;
-  GRPC_CHTTP2_STREAM_REF(&s->global, "perform_stream_op");
-  grpc_combiner_execute(exec_ctx, t->executor.combiner,
-                        &op->transport_private.closure, GRPC_ERROR_NONE);
+  GRPC_CHTTP2_STREAM_REF(s, "perform_stream_op");
+  grpc_combiner_execute(exec_ctx, t->combiner, &op->transport_private.closure,
+                        GRPC_ERROR_NONE, op->covered_by_poller);
   GPR_TIMER_END("perform_stream_op", 0);
 }
 
 static void send_ping_locked(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
                              grpc_closure *on_recv) {
   grpc_chttp2_outstanding_ping *p = gpr_malloc(sizeof(*p));
-  p->next = &t->global.pings;
+  p->next = &t->pings;
   p->prev = p->next->prev;
   p->prev->next = p->next->prev = p;
-  p->id[0] = (uint8_t)((t->global.ping_counter >> 56) & 0xff);
-  p->id[1] = (uint8_t)((t->global.ping_counter >> 48) & 0xff);
-  p->id[2] = (uint8_t)((t->global.ping_counter >> 40) & 0xff);
-  p->id[3] = (uint8_t)((t->global.ping_counter >> 32) & 0xff);
-  p->id[4] = (uint8_t)((t->global.ping_counter >> 24) & 0xff);
-  p->id[5] = (uint8_t)((t->global.ping_counter >> 16) & 0xff);
-  p->id[6] = (uint8_t)((t->global.ping_counter >> 8) & 0xff);
-  p->id[7] = (uint8_t)(t->global.ping_counter & 0xff);
+  p->id[0] = (uint8_t)((t->ping_counter >> 56) & 0xff);
+  p->id[1] = (uint8_t)((t->ping_counter >> 48) & 0xff);
+  p->id[2] = (uint8_t)((t->ping_counter >> 40) & 0xff);
+  p->id[3] = (uint8_t)((t->ping_counter >> 32) & 0xff);
+  p->id[4] = (uint8_t)((t->ping_counter >> 24) & 0xff);
+  p->id[5] = (uint8_t)((t->ping_counter >> 16) & 0xff);
+  p->id[6] = (uint8_t)((t->ping_counter >> 8) & 0xff);
+  p->id[7] = (uint8_t)(t->ping_counter & 0xff);
+  t->ping_counter++;
   p->on_recv = on_recv;
-  gpr_slice_buffer_add(&t->global.qbuf, grpc_chttp2_ping_create(0, p->id));
-  grpc_chttp2_initiate_write(exec_ctx, &t->global, true, "send_ping");
+  gpr_slice_buffer_add(&t->qbuf, grpc_chttp2_ping_create(0, p->id));
+  grpc_chttp2_initiate_write(exec_ctx, t, true, "send_ping");
 }
 
-typedef struct ack_ping_args {
-  grpc_closure closure;
-  grpc_chttp2_transport *t;
-  uint8_t opaque_8bytes[8];
-} ack_ping_args;
-
-static void ack_ping_locked(grpc_exec_ctx *exec_ctx, void *a,
-                            grpc_error *error_ignored) {
-  ack_ping_args *args = a;
+void grpc_chttp2_ack_ping(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                          const uint8_t *opaque_8bytes) {
   grpc_chttp2_outstanding_ping *ping;
-  grpc_chttp2_transport_global *transport_global = &args->t->global;
-  for (ping = transport_global->pings.next; ping != &transport_global->pings;
-       ping = ping->next) {
-    if (0 == memcmp(args->opaque_8bytes, ping->id, 8)) {
+  for (ping = t->pings.next; ping != &t->pings; ping = ping->next) {
+    if (0 == memcmp(opaque_8bytes, ping->id, 8)) {
       grpc_exec_ctx_sched(exec_ctx, ping->on_recv, GRPC_ERROR_NONE, NULL);
       ping->next->prev = ping->prev;
       ping->prev->next = ping->next;
       gpr_free(ping);
-      break;
+      return;
     }
   }
-  UNREF_TRANSPORT(exec_ctx, args->t, "ack_ping");
-  gpr_free(args);
-}
-
-void grpc_chttp2_ack_ping(grpc_exec_ctx *exec_ctx,
-                          grpc_chttp2_transport_parsing *transport_parsing,
-                          const uint8_t *opaque_8bytes) {
-  ack_ping_args *args = gpr_malloc(sizeof(*args));
-  args->t = TRANSPORT_FROM_PARSING(transport_parsing);
-  memcpy(args->opaque_8bytes, opaque_8bytes, sizeof(args->opaque_8bytes));
-  grpc_closure_init(&args->closure, ack_ping_locked, args);
-  REF_TRANSPORT(args->t, "ack_ping");
-  grpc_combiner_execute(exec_ctx, args->t->executor.combiner, &args->closure,
-                        GRPC_ERROR_NONE);
+  char *msg = gpr_dump((const char *)opaque_8bytes, 8, GPR_DUMP_HEX);
+  char *from = grpc_endpoint_get_peer(t->ep);
+  gpr_log(GPR_DEBUG, "Unknown ping response from %s: %s", from, msg);
+  gpr_free(from);
+  gpr_free(msg);
 }
 
 static void perform_transport_op_locked(grpc_exec_ctx *exec_ctx,
@@ -1293,15 +1193,6 @@
   grpc_chttp2_transport *t = op->transport_private.args[0];
   grpc_error *close_transport = op->disconnect_with_error;
 
-  /* If there's a set_accept_stream ensure that we're not parsing
-     to avoid changing things out from underneath */
-  if (t->executor.parsing_active && op->set_accept_stream) {
-    GPR_ASSERT(t->post_parsing_op == NULL);
-    t->post_parsing_op = gpr_malloc(sizeof(*op));
-    memcpy(t->post_parsing_op, op, sizeof(*op));
-    return;
-  }
-
   if (op->on_connectivity_state_change != NULL) {
     grpc_connectivity_state_notify_on_state_change(
         exec_ctx, &t->channel_callback.state_tracker, op->connectivity_state,
@@ -1309,15 +1200,15 @@
   }
 
   if (op->send_goaway) {
-    t->global.sent_goaway = 1;
+    t->sent_goaway = 1;
     grpc_chttp2_goaway_append(
-        t->global.last_incoming_stream_id,
+        t->last_new_stream_id,
         (uint32_t)grpc_chttp2_grpc_status_to_http2_error(op->goaway_status),
-        gpr_slice_ref(*op->goaway_message), &t->global.qbuf);
-    close_transport = grpc_chttp2_has_streams(t)
-                          ? GRPC_ERROR_NONE
-                          : GRPC_ERROR_CREATE("GOAWAY sent");
-    grpc_chttp2_initiate_write(exec_ctx, &t->global, false, "goaway_sent");
+        gpr_slice_ref(*op->goaway_message), &t->qbuf);
+    close_transport = grpc_chttp2_stream_map_size(&t->stream_map) == 0
+                          ? GRPC_ERROR_CREATE("GOAWAY sent")
+                          : GRPC_ERROR_NONE;
+    grpc_chttp2_initiate_write(exec_ctx, t, false, "goaway_sent");
   }
 
   if (op->set_accept_stream) {
@@ -1342,154 +1233,127 @@
     close_transport_locked(exec_ctx, t, close_transport);
   }
 
-  grpc_exec_ctx_sched(exec_ctx, op->on_consumed, GRPC_ERROR_NONE, NULL);
+  grpc_closure_run(exec_ctx, op->on_consumed, GRPC_ERROR_NONE);
 
-  UNREF_TRANSPORT(exec_ctx, t, "transport_op");
+  GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "transport_op");
 }
 
 static void perform_transport_op(grpc_exec_ctx *exec_ctx, grpc_transport *gt,
                                  grpc_transport_op *op) {
   grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt;
+  char *msg = grpc_transport_op_string(op);
+  gpr_free(msg);
   op->transport_private.args[0] = gt;
   grpc_closure_init(&op->transport_private.closure, perform_transport_op_locked,
                     op);
-  REF_TRANSPORT(t, "transport_op");
-  grpc_combiner_execute(exec_ctx, t->executor.combiner,
-                        &op->transport_private.closure, GRPC_ERROR_NONE);
+  GRPC_CHTTP2_REF_TRANSPORT(t, "transport_op");
+  grpc_combiner_execute(exec_ctx, t->combiner, &op->transport_private.closure,
+                        GRPC_ERROR_NONE, false);
 }
 
 /*******************************************************************************
  * INPUT PROCESSING - GENERAL
  */
 
-static void check_read_ops(grpc_exec_ctx *exec_ctx,
-                           grpc_chttp2_transport_global *transport_global) {
-  GPR_TIMER_BEGIN("check_read_ops", 0);
-  grpc_chttp2_stream_global *stream_global;
+void grpc_chttp2_maybe_complete_recv_initial_metadata(grpc_exec_ctx *exec_ctx,
+                                                      grpc_chttp2_transport *t,
+                                                      grpc_chttp2_stream *s) {
   grpc_byte_stream *bs;
-  while (
-      grpc_chttp2_list_pop_check_read_ops(transport_global, &stream_global)) {
-    if (stream_global->recv_initial_metadata_ready != NULL &&
-        stream_global->published_initial_metadata) {
-      if (stream_global->seen_error) {
-        while ((bs = grpc_chttp2_incoming_frame_queue_pop(
-                    &stream_global->incoming_frames)) != NULL) {
-          incoming_byte_stream_destroy_locked(exec_ctx, bs, GRPC_ERROR_NONE);
-        }
-        if (stream_global->exceeded_metadata_size) {
-          cancel_from_api(
-              exec_ctx, transport_global, stream_global,
-              grpc_error_set_int(
-                  GRPC_ERROR_CREATE(
-                      "received initial metadata size exceeds limit"),
-                  GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_RESOURCE_EXHAUSTED));
-        }
-      }
-      grpc_chttp2_incoming_metadata_buffer_publish(
-          &stream_global->received_initial_metadata,
-          stream_global->recv_initial_metadata);
-      grpc_exec_ctx_sched(exec_ctx, stream_global->recv_initial_metadata_ready,
-                          GRPC_ERROR_NONE, NULL);
-      stream_global->recv_initial_metadata_ready = NULL;
-    }
-    if (stream_global->recv_message_ready != NULL) {
-      while (stream_global->final_metadata_requested &&
-             stream_global->seen_error &&
-             (bs = grpc_chttp2_incoming_frame_queue_pop(
-                  &stream_global->incoming_frames)) != NULL) {
+  if (s->recv_initial_metadata_ready != NULL &&
+      s->published_metadata[0] != GRPC_METADATA_NOT_PUBLISHED) {
+    if (s->seen_error) {
+      while ((bs = grpc_chttp2_incoming_frame_queue_pop(&s->incoming_frames)) !=
+             NULL) {
         incoming_byte_stream_destroy_locked(exec_ctx, bs, GRPC_ERROR_NONE);
       }
-      if (stream_global->incoming_frames.head != NULL) {
-        *stream_global->recv_message = grpc_chttp2_incoming_frame_queue_pop(
-            &stream_global->incoming_frames);
-        GPR_ASSERT(*stream_global->recv_message != NULL);
-        grpc_exec_ctx_sched(exec_ctx, stream_global->recv_message_ready,
-                            GRPC_ERROR_NONE, NULL);
-        stream_global->recv_message_ready = NULL;
-      } else if (stream_global->published_trailing_metadata) {
-        *stream_global->recv_message = NULL;
-        grpc_exec_ctx_sched(exec_ctx, stream_global->recv_message_ready,
-                            GRPC_ERROR_NONE, NULL);
-        stream_global->recv_message_ready = NULL;
-      }
     }
-    if (stream_global->recv_trailing_metadata_finished != NULL &&
-        stream_global->read_closed && stream_global->write_closed) {
-      if (stream_global->seen_error) {
-        while ((bs = grpc_chttp2_incoming_frame_queue_pop(
-                    &stream_global->incoming_frames)) != NULL) {
-          incoming_byte_stream_destroy_locked(exec_ctx, bs, GRPC_ERROR_NONE);
-        }
-        if (stream_global->exceeded_metadata_size) {
-          cancel_from_api(
-              exec_ctx, transport_global, stream_global,
-              grpc_error_set_int(
-                  GRPC_ERROR_CREATE(
-                      "received trailing metadata size exceeds limit"),
-                  GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_RESOURCE_EXHAUSTED));
-        }
-      }
-      if (stream_global->all_incoming_byte_streams_finished) {
-        grpc_chttp2_incoming_metadata_buffer_publish(
-            &stream_global->received_trailing_metadata,
-            stream_global->recv_trailing_metadata);
-        grpc_chttp2_complete_closure_step(
-            exec_ctx, transport_global, stream_global,
-            &stream_global->recv_trailing_metadata_finished, GRPC_ERROR_NONE);
-      }
-    }
+    grpc_chttp2_incoming_metadata_buffer_publish(&s->metadata_buffer[0],
+                                                 s->recv_initial_metadata);
+    null_then_run_closure(exec_ctx, &s->recv_initial_metadata_ready,
+                          GRPC_ERROR_NONE);
   }
-  GPR_TIMER_END("check_read_ops", 0);
 }
 
-static void decrement_active_streams_locked(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  if ((stream_global->all_incoming_byte_streams_finished =
-           gpr_unref(&stream_global->active_streams))) {
-    grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                        stream_global);
+void grpc_chttp2_maybe_complete_recv_message(grpc_exec_ctx *exec_ctx,
+                                             grpc_chttp2_transport *t,
+                                             grpc_chttp2_stream *s) {
+  grpc_byte_stream *bs;
+  if (s->recv_message_ready != NULL) {
+    while (s->final_metadata_requested && s->seen_error &&
+           (bs = grpc_chttp2_incoming_frame_queue_pop(&s->incoming_frames)) !=
+               NULL) {
+      incoming_byte_stream_destroy_locked(exec_ctx, bs, GRPC_ERROR_NONE);
+    }
+    if (s->incoming_frames.head != NULL) {
+      *s->recv_message =
+          grpc_chttp2_incoming_frame_queue_pop(&s->incoming_frames);
+      GPR_ASSERT(*s->recv_message != NULL);
+      null_then_run_closure(exec_ctx, &s->recv_message_ready, GRPC_ERROR_NONE);
+    } else if (s->published_metadata[1] != GRPC_METADATA_NOT_PUBLISHED) {
+      *s->recv_message = NULL;
+      null_then_run_closure(exec_ctx, &s->recv_message_ready, GRPC_ERROR_NONE);
+    }
+  }
+}
+
+void grpc_chttp2_maybe_complete_recv_trailing_metadata(grpc_exec_ctx *exec_ctx,
+                                                       grpc_chttp2_transport *t,
+                                                       grpc_chttp2_stream *s) {
+  grpc_byte_stream *bs;
+  grpc_chttp2_maybe_complete_recv_message(exec_ctx, t, s);
+  if (s->recv_trailing_metadata_finished != NULL && s->read_closed &&
+      s->write_closed) {
+    if (s->seen_error) {
+      while ((bs = grpc_chttp2_incoming_frame_queue_pop(&s->incoming_frames)) !=
+             NULL) {
+        incoming_byte_stream_destroy_locked(exec_ctx, bs, GRPC_ERROR_NONE);
+      }
+    }
+    if (s->all_incoming_byte_streams_finished &&
+        s->recv_trailing_metadata_finished != NULL) {
+      grpc_chttp2_incoming_metadata_buffer_publish(&s->metadata_buffer[1],
+                                                   s->recv_trailing_metadata);
+      grpc_chttp2_complete_closure_step(
+          exec_ctx, t, s, &s->recv_trailing_metadata_finished, GRPC_ERROR_NONE,
+          "recv_trailing_metadata_finished");
+    }
+  }
+}
+
+static void decrement_active_streams_locked(grpc_exec_ctx *exec_ctx,
+                                            grpc_chttp2_transport *t,
+                                            grpc_chttp2_stream *s) {
+  if ((s->all_incoming_byte_streams_finished = gpr_unref(&s->active_streams))) {
+    grpc_chttp2_maybe_complete_recv_trailing_metadata(exec_ctx, t, s);
   }
 }
 
 static void remove_stream(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
                           uint32_t id, grpc_error *error) {
-  size_t new_stream_count;
-  grpc_chttp2_stream *s =
-      grpc_chttp2_stream_map_delete(&t->parsing_stream_map, id);
-  if (!s) {
-    s = grpc_chttp2_stream_map_delete(&t->new_stream_map, id);
-  }
+  grpc_chttp2_stream *s = grpc_chttp2_stream_map_delete(&t->stream_map, id);
   GPR_ASSERT(s);
-  s->global.in_stream_map = false;
-  if (t->parsing.incoming_stream == &s->parsing) {
-    t->parsing.incoming_stream = NULL;
-    grpc_chttp2_parsing_become_skip_parser(exec_ctx, &t->parsing);
+  if (t->incoming_stream == s) {
+    t->incoming_stream = NULL;
+    grpc_chttp2_parsing_become_skip_parser(exec_ctx, t);
   }
-  if (s->parsing.data_parser.parsing_frame != NULL) {
+  if (s->data_parser.parsing_frame != NULL) {
     grpc_chttp2_incoming_byte_stream_finished(
-        exec_ctx, s->parsing.data_parser.parsing_frame, GRPC_ERROR_REF(error),
-        0);
-    s->parsing.data_parser.parsing_frame = NULL;
+        exec_ctx, s->data_parser.parsing_frame, GRPC_ERROR_REF(error));
+    s->data_parser.parsing_frame = NULL;
   }
 
-  if (grpc_chttp2_unregister_stream(t, s) && t->global.sent_goaway) {
+  if (grpc_chttp2_stream_map_size(&t->stream_map) == 0 && t->sent_goaway) {
     close_transport_locked(
         exec_ctx, t, GRPC_ERROR_CREATE_REFERENCING(
                          "Last stream closed after sending GOAWAY", &error, 1));
   }
-  if (grpc_chttp2_list_remove_writable_stream(&t->global, &s->global)) {
-    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, &s->global, "chttp2_writing");
+  if (grpc_chttp2_list_remove_writable_stream(t, s)) {
+    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2_writing:remove_stream");
   }
 
-  new_stream_count = grpc_chttp2_stream_map_size(&t->parsing_stream_map) +
-                     grpc_chttp2_stream_map_size(&t->new_stream_map);
-  GPR_ASSERT(new_stream_count <= UINT32_MAX);
-  if (new_stream_count != t->global.concurrent_stream_count) {
-    t->global.concurrent_stream_count = (uint32_t)new_stream_count;
-    maybe_start_some_streams(exec_ctx, &t->global);
-  }
   GRPC_ERROR_UNREF(error);
+
+  maybe_start_some_streams(exec_ctx, t);
 }
 
 static void status_codes_from_error(grpc_error *error, gpr_timespec deadline,
@@ -1519,23 +1383,20 @@
   }
 }
 
-static void cancel_from_api(grpc_exec_ctx *exec_ctx,
-                            grpc_chttp2_transport_global *transport_global,
-                            grpc_chttp2_stream_global *stream_global,
-                            grpc_error *due_to_error) {
-  if (!stream_global->read_closed || !stream_global->write_closed) {
+void grpc_chttp2_cancel_stream(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_transport *t, grpc_chttp2_stream *s,
+                               grpc_error *due_to_error) {
+  if (!s->read_closed || !s->write_closed) {
     grpc_status_code grpc_status;
     grpc_chttp2_error_code http_error;
-    status_codes_from_error(due_to_error, stream_global->deadline, &http_error,
+    status_codes_from_error(due_to_error, s->deadline, &http_error,
                             &grpc_status);
 
-    if (stream_global->id != 0) {
+    if (s->id != 0) {
       gpr_slice_buffer_add(
-          &transport_global->qbuf,
-          grpc_chttp2_rst_stream_create(stream_global->id, (uint32_t)http_error,
-                                        &stream_global->stats.outgoing));
-      grpc_chttp2_initiate_write(exec_ctx, transport_global, false,
-                                 "rst_stream");
+          &t->qbuf, grpc_chttp2_rst_stream_create(s->id, (uint32_t)http_error,
+                                                  &s->stats.outgoing));
+      grpc_chttp2_initiate_write(exec_ctx, t, false, "rst_stream");
     }
 
     const char *msg =
@@ -1546,27 +1407,21 @@
       msg = grpc_error_string(due_to_error);
     }
     gpr_slice msg_slice = gpr_slice_from_copied_string(msg);
-    grpc_chttp2_fake_status(exec_ctx, transport_global, stream_global,
-                            grpc_status, &msg_slice);
+    grpc_chttp2_fake_status(exec_ctx, t, s, grpc_status, &msg_slice);
     if (free_msg) grpc_error_free_string(msg);
   }
-  if (due_to_error != GRPC_ERROR_NONE && !stream_global->seen_error) {
-    stream_global->seen_error = true;
-    grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                        stream_global);
+  if (due_to_error != GRPC_ERROR_NONE && !s->seen_error) {
+    s->seen_error = true;
+    grpc_chttp2_maybe_complete_recv_trailing_metadata(exec_ctx, t, s);
   }
-  grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, 1,
-                                 1, due_to_error);
+  grpc_chttp2_mark_stream_closed(exec_ctx, t, s, 1, 1, due_to_error);
 }
 
-void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx,
-                             grpc_chttp2_transport_global *transport_global,
-                             grpc_chttp2_stream_global *stream_global,
-                             grpc_status_code status, gpr_slice *slice) {
+void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                             grpc_chttp2_stream *s, grpc_status_code status,
+                             gpr_slice *slice) {
   if (status != GRPC_STATUS_OK) {
-    stream_global->seen_error = true;
-    grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                        stream_global);
+    s->seen_error = true;
   }
   /* stream_global->recv_trailing_metadata_finished gives us a
      last chance replacement: we've received trailing metadata,
@@ -1574,24 +1429,23 @@
      to the upper layers - drop what we've got, and then publish
      what we want - which is safe because we haven't told anyone
      about the metadata yet */
-  if (!stream_global->published_trailing_metadata ||
-      stream_global->recv_trailing_metadata_finished != NULL) {
+  if (s->published_metadata[1] == GRPC_METADATA_NOT_PUBLISHED ||
+      s->recv_trailing_metadata_finished != NULL) {
     char status_string[GPR_LTOA_MIN_BUFSIZE];
     gpr_ltoa(status, status_string);
     grpc_chttp2_incoming_metadata_buffer_add(
-        &stream_global->received_trailing_metadata,
+        &s->metadata_buffer[1],
         grpc_mdelem_from_metadata_strings(
             GRPC_MDSTR_GRPC_STATUS, grpc_mdstr_from_string(status_string)));
     if (slice) {
       grpc_chttp2_incoming_metadata_buffer_add(
-          &stream_global->received_trailing_metadata,
+          &s->metadata_buffer[1],
           grpc_mdelem_from_metadata_strings(
               GRPC_MDSTR_GRPC_MESSAGE,
               grpc_mdstr_from_slice(gpr_slice_ref(*slice))));
     }
-    stream_global->published_trailing_metadata = true;
-    grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                        stream_global);
+    s->published_metadata[1] = GRPC_METADATA_SYNTHESIZED_FROM_FAKE;
+    grpc_chttp2_maybe_complete_recv_trailing_metadata(exec_ctx, t, s);
   }
   if (slice) {
     gpr_slice_unref(*slice);
@@ -1609,91 +1463,88 @@
   ++*nrefs;
 }
 
-static grpc_error *removal_error(grpc_error *extra_error,
-                                 grpc_chttp2_stream_global *stream_global) {
+static grpc_error *removal_error(grpc_error *extra_error, grpc_chttp2_stream *s,
+                                 const char *master_error_msg) {
   grpc_error *refs[3];
   size_t nrefs = 0;
-  add_error(stream_global->read_closed_error, refs, &nrefs);
-  add_error(stream_global->write_closed_error, refs, &nrefs);
+  add_error(s->read_closed_error, refs, &nrefs);
+  add_error(s->write_closed_error, refs, &nrefs);
   add_error(extra_error, refs, &nrefs);
   grpc_error *error = GRPC_ERROR_NONE;
   if (nrefs > 0) {
-    error = GRPC_ERROR_CREATE_REFERENCING("Failed due to stream removal", refs,
-                                          nrefs);
+    error = GRPC_ERROR_CREATE_REFERENCING(master_error_msg, refs, nrefs);
   }
   GRPC_ERROR_UNREF(extra_error);
   return error;
 }
 
 static void fail_pending_writes(grpc_exec_ctx *exec_ctx,
-                                grpc_chttp2_transport_global *transport_global,
-                                grpc_chttp2_stream_global *stream_global,
+                                grpc_chttp2_transport *t, grpc_chttp2_stream *s,
                                 grpc_error *error) {
-  error = removal_error(error, stream_global);
-  stream_global->send_message = NULL;
+  error =
+      removal_error(error, s, "Pending writes failed due to stream closure");
+  s->fetching_send_message = NULL;
   grpc_chttp2_complete_closure_step(
-      exec_ctx, transport_global, stream_global,
-      &stream_global->send_initial_metadata_finished, GRPC_ERROR_REF(error));
+      exec_ctx, t, s, &s->send_initial_metadata_finished, GRPC_ERROR_REF(error),
+      "send_initial_metadata_finished");
   grpc_chttp2_complete_closure_step(
-      exec_ctx, transport_global, stream_global,
-      &stream_global->send_trailing_metadata_finished, GRPC_ERROR_REF(error));
-  grpc_chttp2_complete_closure_step(exec_ctx, transport_global, stream_global,
-                                    &stream_global->send_message_finished,
-                                    error);
-}
-
-void grpc_chttp2_mark_stream_closed(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, int close_reads, int close_writes,
-    grpc_error *error) {
-  if (stream_global->read_closed && stream_global->write_closed) {
-    /* already closed */
-    GRPC_ERROR_UNREF(error);
-    return;
-  }
-  grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                      stream_global);
-  if (close_reads && !stream_global->read_closed) {
-    stream_global->read_closed_error = GRPC_ERROR_REF(error);
-    stream_global->read_closed = true;
-    stream_global->published_initial_metadata = true;
-    stream_global->published_trailing_metadata = true;
-    decrement_active_streams_locked(exec_ctx, transport_global, stream_global);
-  }
-  if (close_writes && !stream_global->write_closed) {
-    stream_global->write_closed_error = GRPC_ERROR_REF(error);
-    stream_global->write_closed = true;
-    if (TRANSPORT_FROM_GLOBAL(transport_global)->executor.write_state !=
-        GRPC_CHTTP2_WRITING_INACTIVE) {
-      GRPC_CHTTP2_STREAM_REF(stream_global, "finish_writes");
-      grpc_chttp2_list_add_closed_waiting_for_writing(transport_global,
-                                                      stream_global);
-    } else {
-      fail_pending_writes(exec_ctx, transport_global, stream_global,
-                          GRPC_ERROR_REF(error));
-    }
-  }
-  if (stream_global->read_closed && stream_global->write_closed) {
-    if (stream_global->id != 0 &&
-        TRANSPORT_FROM_GLOBAL(transport_global)->executor.parsing_active) {
-      grpc_chttp2_list_add_closed_waiting_for_parsing(transport_global,
-                                                      stream_global);
-    } else {
-      if (stream_global->id != 0) {
-        remove_stream(exec_ctx, TRANSPORT_FROM_GLOBAL(transport_global),
-                      stream_global->id,
-                      removal_error(GRPC_ERROR_REF(error), stream_global));
-      }
-      GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2");
-    }
+      exec_ctx, t, s, &s->send_trailing_metadata_finished,
+      GRPC_ERROR_REF(error), "send_trailing_metadata_finished");
+  grpc_chttp2_complete_closure_step(
+      exec_ctx, t, s, &s->fetching_send_message_finished, GRPC_ERROR_REF(error),
+      "fetching_send_message_finished");
+  while (s->on_write_finished_cbs) {
+    grpc_chttp2_write_cb *cb = s->on_write_finished_cbs;
+    s->on_write_finished_cbs = cb->next;
+    grpc_chttp2_complete_closure_step(exec_ctx, t, s, &cb->closure,
+                                      GRPC_ERROR_REF(error),
+                                      "on_write_finished_cb");
+    cb->next = t->write_cb_pool;
+    t->write_cb_pool = cb;
   }
   GRPC_ERROR_UNREF(error);
 }
 
-static void close_from_api(grpc_exec_ctx *exec_ctx,
-                           grpc_chttp2_transport_global *transport_global,
-                           grpc_chttp2_stream_global *stream_global,
-                           grpc_error *error) {
+void grpc_chttp2_mark_stream_closed(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_transport *t,
+                                    grpc_chttp2_stream *s, int close_reads,
+                                    int close_writes, grpc_error *error) {
+  if (s->read_closed && s->write_closed) {
+    /* already closed */
+    GRPC_ERROR_UNREF(error);
+    return;
+  }
+  if (close_reads && !s->read_closed) {
+    s->read_closed_error = GRPC_ERROR_REF(error);
+    s->read_closed = true;
+    for (int i = 0; i < 2; i++) {
+      if (s->published_metadata[i] == GRPC_METADATA_NOT_PUBLISHED) {
+        s->published_metadata[i] = GPRC_METADATA_PUBLISHED_AT_CLOSE;
+      }
+    }
+    decrement_active_streams_locked(exec_ctx, t, s);
+    grpc_chttp2_maybe_complete_recv_initial_metadata(exec_ctx, t, s);
+    grpc_chttp2_maybe_complete_recv_message(exec_ctx, t, s);
+    grpc_chttp2_maybe_complete_recv_trailing_metadata(exec_ctx, t, s);
+  }
+  if (close_writes && !s->write_closed) {
+    s->write_closed_error = GRPC_ERROR_REF(error);
+    s->write_closed = true;
+    fail_pending_writes(exec_ctx, t, s, GRPC_ERROR_REF(error));
+    grpc_chttp2_maybe_complete_recv_trailing_metadata(exec_ctx, t, s);
+  }
+  if (s->read_closed && s->write_closed) {
+    if (s->id != 0) {
+      remove_stream(exec_ctx, t, s->id,
+                    removal_error(GRPC_ERROR_REF(error), s, "Stream removed"));
+    }
+    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2");
+  }
+  GRPC_ERROR_UNREF(error);
+}
+
+static void close_from_api(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                           grpc_chttp2_stream *s, grpc_error *error) {
   gpr_slice hdr;
   gpr_slice status_hdr;
   gpr_slice message_pfx;
@@ -1701,16 +1552,17 @@
   uint32_t len = 0;
   grpc_status_code grpc_status;
   grpc_chttp2_error_code http_error;
-  status_codes_from_error(error, stream_global->deadline, &http_error,
-                          &grpc_status);
+  status_codes_from_error(error, s->deadline, &http_error, &grpc_status);
 
   GPR_ASSERT(grpc_status >= 0 && (int)grpc_status < 100);
 
-  if (stream_global->id != 0 && !transport_global->is_client) {
+  if (s->id != 0 && !t->is_client) {
     /* Hand roll a header block.
-       This is unnecessarily ugly - at some point we should find a more elegant
+       This is unnecessarily ugly - at some point we should find a more
+       elegant
        solution.
-       It's complicated by the fact that our send machinery would be dead by the
+       It's complicated by the fact that our send machinery would be dead by
+       the
        time we got around to sending this, so instead we ignore HPACK
        compression
        and just write the uncompressed bytes onto the wire. */
@@ -1775,23 +1627,22 @@
     *p++ = (uint8_t)(len);
     *p++ = GRPC_CHTTP2_FRAME_HEADER;
     *p++ = GRPC_CHTTP2_DATA_FLAG_END_STREAM | GRPC_CHTTP2_DATA_FLAG_END_HEADERS;
-    *p++ = (uint8_t)(stream_global->id >> 24);
-    *p++ = (uint8_t)(stream_global->id >> 16);
-    *p++ = (uint8_t)(stream_global->id >> 8);
-    *p++ = (uint8_t)(stream_global->id);
+    *p++ = (uint8_t)(s->id >> 24);
+    *p++ = (uint8_t)(s->id >> 16);
+    *p++ = (uint8_t)(s->id >> 8);
+    *p++ = (uint8_t)(s->id);
     GPR_ASSERT(p == GPR_SLICE_END_PTR(hdr));
 
-    gpr_slice_buffer_add(&transport_global->qbuf, hdr);
-    gpr_slice_buffer_add(&transport_global->qbuf, status_hdr);
+    gpr_slice_buffer_add(&t->qbuf, hdr);
+    gpr_slice_buffer_add(&t->qbuf, status_hdr);
     if (optional_message) {
-      gpr_slice_buffer_add(&transport_global->qbuf, message_pfx);
-      gpr_slice_buffer_add(&transport_global->qbuf,
+      gpr_slice_buffer_add(&t->qbuf, message_pfx);
+      gpr_slice_buffer_add(&t->qbuf,
                            gpr_slice_from_copied_string(optional_message));
     }
     gpr_slice_buffer_add(
-        &transport_global->qbuf,
-        grpc_chttp2_rst_stream_create(stream_global->id, GRPC_CHTTP2_NO_ERROR,
-                                      &stream_global->stats.outgoing));
+        &t->qbuf, grpc_chttp2_rst_stream_create(s->id, GRPC_CHTTP2_NO_ERROR,
+                                                &s->stats.outgoing));
   }
 
   const char *msg = grpc_error_get_str(error, GRPC_ERROR_STR_GRPC_MESSAGE);
@@ -1801,46 +1652,33 @@
     msg = grpc_error_string(error);
   }
   gpr_slice msg_slice = gpr_slice_from_copied_string(msg);
-  grpc_chttp2_fake_status(exec_ctx, transport_global, stream_global,
-                          grpc_status, &msg_slice);
+  grpc_chttp2_fake_status(exec_ctx, t, s, grpc_status, &msg_slice);
   if (free_msg) grpc_error_free_string(msg);
 
-  grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, 1,
-                                 1, error);
-  grpc_chttp2_initiate_write(exec_ctx, transport_global, false,
-                             "close_from_api");
+  grpc_chttp2_mark_stream_closed(exec_ctx, t, s, 1, 1, error);
+  grpc_chttp2_initiate_write(exec_ctx, t, false, "close_from_api");
 }
 
 typedef struct {
   grpc_exec_ctx *exec_ctx;
   grpc_error *error;
+  grpc_chttp2_transport *t;
 } cancel_stream_cb_args;
 
-static void cancel_stream_cb(grpc_chttp2_transport_global *transport_global,
-                             void *user_data,
-                             grpc_chttp2_stream_global *stream_global) {
+static void cancel_stream_cb(void *user_data, uint32_t key, void *stream) {
   cancel_stream_cb_args *args = user_data;
-  cancel_from_api(args->exec_ctx, transport_global, stream_global,
-                  GRPC_ERROR_REF(args->error));
+  grpc_chttp2_stream *s = stream;
+  grpc_chttp2_cancel_stream(args->exec_ctx, args->t, s,
+                            GRPC_ERROR_REF(args->error));
 }
 
 static void end_all_the_calls(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
                               grpc_error *error) {
-  cancel_stream_cb_args args = {exec_ctx, error};
-  grpc_chttp2_for_all_streams(&t->global, &args, cancel_stream_cb);
+  cancel_stream_cb_args args = {exec_ctx, error, t};
+  grpc_chttp2_stream_map_for_each(&t->stream_map, cancel_stream_cb, &args);
   GRPC_ERROR_UNREF(error);
 }
 
-static void drop_connection(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
-                            grpc_error *error) {
-  if (!grpc_error_get_int(error, GRPC_ERROR_INT_GRPC_STATUS, NULL)) {
-    error = grpc_error_set_int(error, GRPC_ERROR_INT_GRPC_STATUS,
-                               GRPC_STATUS_UNAVAILABLE);
-  }
-  close_transport_locked(exec_ctx, t, GRPC_ERROR_REF(error));
-  end_all_the_calls(exec_ctx, t, error);
-}
-
 /** update window from a settings change */
 typedef struct {
   grpc_chttp2_transport *t;
@@ -1851,20 +1689,23 @@
   update_global_window_args *a = args;
   grpc_chttp2_transport *t = a->t;
   grpc_chttp2_stream *s = stream;
-  grpc_chttp2_transport_global *transport_global = &t->global;
-  grpc_chttp2_stream_global *stream_global = &s->global;
   int was_zero;
   int is_zero;
-  int64_t initial_window_update = t->parsing.initial_window_update;
+  int64_t initial_window_update = t->initial_window_update;
 
-  was_zero = stream_global->outgoing_window <= 0;
-  GRPC_CHTTP2_FLOW_CREDIT_STREAM("settings", transport_global, stream_global,
-                                 outgoing_window, initial_window_update);
-  is_zero = stream_global->outgoing_window <= 0;
+  if (initial_window_update > 0) {
+    was_zero = s->outgoing_window <= 0;
+    GRPC_CHTTP2_FLOW_CREDIT_STREAM("settings", t, s, outgoing_window,
+                                   initial_window_update);
+    is_zero = s->outgoing_window <= 0;
 
-  if (was_zero && !is_zero) {
-    grpc_chttp2_become_writable(a->exec_ctx, transport_global, stream_global,
-                                true, "update_global_window");
+    if (was_zero && !is_zero) {
+      grpc_chttp2_become_writable(a->exec_ctx, t, s, true,
+                                  "update_global_window");
+    }
+  } else {
+    GRPC_CHTTP2_FLOW_DEBIT_STREAM("settings", t, s, outgoing_window,
+                                  -initial_window_update);
   }
 }
 
@@ -1872,50 +1713,19 @@
  * INPUT PROCESSING - PARSING
  */
 
-static void parsing_action(grpc_exec_ctx *exec_ctx, void *arg,
-                           grpc_error *error);
-static void post_reading_action_locked(grpc_exec_ctx *exec_ctx, void *arg,
-                                       grpc_error *error);
-static void post_parse_locked(grpc_exec_ctx *exec_ctx, void *arg,
-                              grpc_error *error);
-
-static void reading_action(grpc_exec_ctx *exec_ctx, void *tp,
-                           grpc_error *error) {
+static void read_action_begin(grpc_exec_ctx *exec_ctx, void *tp,
+                              grpc_error *error) {
   /* Control flow:
      reading_action_locked ->
        (parse_unlocked -> post_parse_locked)? ->
        post_reading_action_locked */
   GPR_TIMER_BEGIN("reading_action", 0);
   grpc_chttp2_transport *t = tp;
-  grpc_combiner_execute(exec_ctx, t->executor.combiner,
-                        &t->reading_action_locked, GRPC_ERROR_REF(error));
+  grpc_combiner_execute(exec_ctx, t->combiner, &t->read_action_locked,
+                        GRPC_ERROR_REF(error), false);
   GPR_TIMER_END("reading_action", 0);
 }
 
-static void reading_action_locked(grpc_exec_ctx *exec_ctx, void *tp,
-                                  grpc_error *error) {
-  GPR_TIMER_BEGIN("reading_action_locked", 0);
-
-  grpc_chttp2_transport *t = tp;
-  grpc_chttp2_transport_global *transport_global = &t->global;
-  grpc_chttp2_transport_parsing *transport_parsing = &t->parsing;
-
-  GPR_ASSERT(!t->executor.parsing_active);
-  if (!t->closed) {
-    t->executor.parsing_active = 1;
-    /* merge stream lists */
-    grpc_chttp2_stream_map_move_into(&t->new_stream_map,
-                                     &t->parsing_stream_map);
-    grpc_chttp2_prepare_to_read(transport_global, transport_parsing);
-    grpc_exec_ctx_sched(exec_ctx, &t->parsing_action, GRPC_ERROR_REF(error),
-                        NULL);
-  } else {
-    post_reading_action_locked(exec_ctx, t, error);
-  }
-
-  GPR_TIMER_END("reading_action_locked", 0);
-}
-
 static grpc_error *try_http_parsing(grpc_exec_ctx *exec_ctx,
                                     grpc_chttp2_transport *t) {
   grpc_http_parser parser;
@@ -1944,131 +1754,102 @@
   return error;
 }
 
-static void parsing_action(grpc_exec_ctx *exec_ctx, void *arg,
-                           grpc_error *error) {
-  grpc_chttp2_transport *t = arg;
-  grpc_error *err = GRPC_ERROR_NONE;
-  GPR_TIMER_BEGIN("reading_action.parse", 0);
-  size_t i = 0;
-  grpc_error *errors[3] = {GRPC_ERROR_REF(error), GRPC_ERROR_NONE,
-                           GRPC_ERROR_NONE};
-  for (; i < t->read_buffer.count && errors[1] == GRPC_ERROR_NONE; i++) {
-    errors[1] = grpc_chttp2_perform_read(exec_ctx, &t->parsing,
-                                         t->read_buffer.slices[i]);
-  };
-  if (errors[1] == GRPC_ERROR_NONE) {
-    err = GRPC_ERROR_REF(error);
-  } else {
-    errors[2] = try_http_parsing(exec_ctx, t);
-    err = GRPC_ERROR_CREATE_REFERENCING("Failed parsing HTTP/2", errors,
-                                        GPR_ARRAY_SIZE(errors));
-  }
-  for (i = 0; i < GPR_ARRAY_SIZE(errors); i++) {
-    GRPC_ERROR_UNREF(errors[i]);
-  }
-  grpc_combiner_execute(exec_ctx, t->executor.combiner, &t->post_parse_locked,
-                        err);
-  GPR_TIMER_END("reading_action.parse", 0);
-}
+static void read_action_locked(grpc_exec_ctx *exec_ctx, void *tp,
+                               grpc_error *error) {
+  GPR_TIMER_BEGIN("reading_action_locked", 0);
 
-static void post_parse_locked(grpc_exec_ctx *exec_ctx, void *arg,
-                              grpc_error *error) {
-  GPR_TIMER_BEGIN("post_parse_locked", 0);
-  grpc_chttp2_transport *t = arg;
-  grpc_chttp2_transport_global *transport_global = &t->global;
-  grpc_chttp2_transport_parsing *transport_parsing = &t->parsing;
-  /* copy parsing qbuf to global qbuf */
-  if (t->parsing.qbuf.count > 0) {
-    gpr_slice_buffer_move_into(&t->parsing.qbuf, &t->global.qbuf);
-    grpc_chttp2_initiate_write(exec_ctx, transport_global, false,
-                               "parsing_qbuf");
-  }
-  /* merge stream lists */
-  grpc_chttp2_stream_map_move_into(&t->new_stream_map, &t->parsing_stream_map);
-  transport_global->concurrent_stream_count =
-      (uint32_t)grpc_chttp2_stream_map_size(&t->parsing_stream_map);
-  if (transport_parsing->initial_window_update != 0) {
-    update_global_window_args args = {t, exec_ctx};
-    grpc_chttp2_stream_map_for_each(&t->parsing_stream_map,
-                                    update_global_window, &args);
-    transport_parsing->initial_window_update = 0;
-  }
-  /* handle higher level things */
-  grpc_chttp2_publish_reads(exec_ctx, transport_global, transport_parsing);
-  t->executor.parsing_active = 0;
-  /* handle delayed transport ops (if there is one) */
-  if (t->post_parsing_op) {
-    grpc_transport_op *op = t->post_parsing_op;
-    t->post_parsing_op = NULL;
-    perform_transport_op_locked(exec_ctx, op, GRPC_ERROR_NONE);
-    gpr_free(op);
-  }
-  /* if a stream is in the stream map, and gets cancelled, we need to
-   * ensure we are not parsing before continuing the cancellation to keep
-   * things in a sane state */
-  grpc_chttp2_stream_global *stream_global;
-  while (grpc_chttp2_list_pop_closed_waiting_for_parsing(transport_global,
-                                                         &stream_global)) {
-    GPR_ASSERT(stream_global->in_stream_map);
-    GPR_ASSERT(stream_global->write_closed);
-    GPR_ASSERT(stream_global->read_closed);
-    remove_stream(exec_ctx, t, stream_global->id,
-                  removal_error(GRPC_ERROR_NONE, stream_global));
-    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2");
-  }
+  grpc_chttp2_transport *t = tp;
 
-  post_reading_action_locked(exec_ctx, t, error);
-  GPR_TIMER_END("post_parse_locked", 0);
-}
-
-static void post_reading_action_locked(grpc_exec_ctx *exec_ctx, void *arg,
-                                       grpc_error *error) {
-  GPR_TIMER_BEGIN("post_reading_action_locked", 0);
-  grpc_chttp2_transport *t = arg;
-  bool keep_reading = false;
   GRPC_ERROR_REF(error);
+
+  grpc_error *err = error;
+  if (err != GRPC_ERROR_NONE) {
+    err = grpc_error_set_int(
+        GRPC_ERROR_CREATE_REFERENCING("Endpoint read failed", &err, 1),
+        GRPC_ERROR_INT_OCCURRED_DURING_WRITE, t->write_state);
+  }
+  GPR_SWAP(grpc_error *, err, error);
+  GRPC_ERROR_UNREF(err);
+  if (!t->closed) {
+    GPR_TIMER_BEGIN("reading_action.parse", 0);
+    size_t i = 0;
+    grpc_error *errors[3] = {GRPC_ERROR_REF(error), GRPC_ERROR_NONE,
+                             GRPC_ERROR_NONE};
+    for (; i < t->read_buffer.count && errors[1] == GRPC_ERROR_NONE; i++) {
+      errors[1] =
+          grpc_chttp2_perform_read(exec_ctx, t, t->read_buffer.slices[i]);
+    };
+    if (errors[1] != GRPC_ERROR_NONE) {
+      errors[2] = try_http_parsing(exec_ctx, t);
+      GRPC_ERROR_UNREF(error);
+      error = GRPC_ERROR_CREATE_REFERENCING("Failed parsing HTTP/2", errors,
+                                            GPR_ARRAY_SIZE(errors));
+    }
+    for (i = 0; i < GPR_ARRAY_SIZE(errors); i++) {
+      GRPC_ERROR_UNREF(errors[i]);
+    }
+    GPR_TIMER_END("reading_action.parse", 0);
+
+    GPR_TIMER_BEGIN("post_parse_locked", 0);
+    if (t->initial_window_update != 0) {
+      update_global_window_args args = {t, exec_ctx};
+      grpc_chttp2_stream_map_for_each(&t->stream_map, update_global_window,
+                                      &args);
+      t->initial_window_update = 0;
+    }
+    /* handle higher level things */
+    if (t->incoming_window < t->connection_window_target * 3 / 4) {
+      int64_t announce_bytes = t->connection_window_target - t->incoming_window;
+      GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parsed", t, announce_incoming_window,
+                                        announce_bytes);
+      GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parsed", t, incoming_window,
+                                        announce_bytes);
+      grpc_chttp2_initiate_write(exec_ctx, t, false, "global incoming window");
+    }
+
+    GPR_TIMER_END("post_parse_locked", 0);
+  }
+
+  GPR_TIMER_BEGIN("post_reading_action_locked", 0);
+  bool keep_reading = false;
   if (error == GRPC_ERROR_NONE && t->closed) {
     error = GRPC_ERROR_CREATE("Transport closed");
   }
   if (error != GRPC_ERROR_NONE) {
-    drop_connection(exec_ctx, t, GRPC_ERROR_REF(error));
+    close_transport_locked(exec_ctx, t, GRPC_ERROR_REF(error));
     t->endpoint_reading = 0;
-    if (grpc_http_write_state_trace) {
-      gpr_log(GPR_DEBUG, "R:%p -> 0 ws=%s", t,
-              write_state_name(t->executor.write_state));
-    }
   } else if (!t->closed) {
     keep_reading = true;
-    REF_TRANSPORT(t, "keep_reading");
-    prevent_endpoint_shutdown(t);
+    GRPC_CHTTP2_REF_TRANSPORT(t, "keep_reading");
   }
   gpr_slice_buffer_reset_and_unref(&t->read_buffer);
 
   if (keep_reading) {
-    grpc_endpoint_read(exec_ctx, t->ep, &t->read_buffer, &t->reading_action);
-    allow_endpoint_shutdown_locked(exec_ctx, t);
-    UNREF_TRANSPORT(exec_ctx, t, "keep_reading");
+    grpc_endpoint_read(exec_ctx, t->ep, &t->read_buffer, &t->read_action_begin);
+    GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "keep_reading");
   } else {
-    UNREF_TRANSPORT(exec_ctx, t, "reading_action");
+    GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "reading_action");
   }
-  GRPC_ERROR_UNREF(error);
 
   GPR_TIMER_END("post_reading_action_locked", 0);
+
+  GRPC_ERROR_UNREF(error);
+
+  GPR_TIMER_END("reading_action_locked", 0);
 }
 
 /*******************************************************************************
  * CALLBACK LOOP
  */
 
-static void connectivity_state_set(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_connectivity_state state, grpc_error *error, const char *reason) {
+static void connectivity_state_set(grpc_exec_ctx *exec_ctx,
+                                   grpc_chttp2_transport *t,
+                                   grpc_connectivity_state state,
+                                   grpc_error *error, const char *reason) {
   GRPC_CHTTP2_IF_TRACING(
       gpr_log(GPR_DEBUG, "set connectivity_state=%d", state));
-  grpc_connectivity_state_set(
-      exec_ctx,
-      &TRANSPORT_FROM_GLOBAL(transport_global)->channel_callback.state_tracker,
-      state, error, reason);
+  grpc_connectivity_state_set(exec_ctx, &t->channel_callback.state_tracker,
+                              state, error, reason);
 }
 
 /*******************************************************************************
@@ -2101,15 +1882,16 @@
   }
 }
 
-static void incoming_byte_stream_update_flow_control(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, size_t max_size_hint,
-    size_t have_already) {
+static void incoming_byte_stream_update_flow_control(grpc_exec_ctx *exec_ctx,
+                                                     grpc_chttp2_transport *t,
+                                                     grpc_chttp2_stream *s,
+                                                     size_t max_size_hint,
+                                                     size_t have_already) {
   uint32_t max_recv_bytes;
 
   /* clamp max recv hint to an allowable size */
-  if (max_size_hint >= UINT32_MAX - transport_global->stream_lookahead) {
-    max_recv_bytes = UINT32_MAX - transport_global->stream_lookahead;
+  if (max_size_hint >= UINT32_MAX - t->stream_lookahead) {
+    max_recv_bytes = UINT32_MAX - t->stream_lookahead;
   } else {
     max_recv_bytes = (uint32_t)max_size_hint;
   }
@@ -2122,23 +1904,21 @@
   }
 
   /* add some small lookahead to keep pipelines flowing */
-  GPR_ASSERT(max_recv_bytes <= UINT32_MAX - transport_global->stream_lookahead);
-  max_recv_bytes += transport_global->stream_lookahead;
-  if (stream_global->max_recv_bytes < max_recv_bytes) {
-    uint32_t add_max_recv_bytes =
-        max_recv_bytes - stream_global->max_recv_bytes;
-    GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", transport_global, stream_global,
-                                   max_recv_bytes, add_max_recv_bytes);
-    GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", transport_global, stream_global,
-                                   unannounced_incoming_window_for_parse,
+  GPR_ASSERT(max_recv_bytes <= UINT32_MAX - t->stream_lookahead);
+  max_recv_bytes += t->stream_lookahead;
+  if (s->max_recv_bytes < max_recv_bytes) {
+    uint32_t add_max_recv_bytes = max_recv_bytes - s->max_recv_bytes;
+    bool new_window_write_is_covered_by_poller =
+        s->max_recv_bytes < have_already;
+    GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", t, s, max_recv_bytes,
                                    add_max_recv_bytes);
-    GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", transport_global, stream_global,
-                                   unannounced_incoming_window_for_writing,
+    GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", t, s, incoming_window,
                                    add_max_recv_bytes);
-    grpc_chttp2_list_add_unannounced_incoming_window_available(transport_global,
-                                                               stream_global);
-    grpc_chttp2_become_writable(exec_ctx, transport_global, stream_global,
-                                false, "read_incoming_stream");
+    GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", t, s, announce_window,
+                                   add_max_recv_bytes);
+    grpc_chttp2_become_writable(exec_ctx, t, s,
+                                new_window_write_is_covered_by_poller,
+                                "read_incoming_stream");
   }
 }
 
@@ -2146,25 +1926,23 @@
                                              void *argp,
                                              grpc_error *error_ignored) {
   grpc_chttp2_incoming_byte_stream *bs = argp;
-  grpc_chttp2_transport_global *transport_global = &bs->transport->global;
-  grpc_chttp2_stream_global *stream_global = &bs->stream->global;
+  grpc_chttp2_transport *t = bs->transport;
+  grpc_chttp2_stream *s = bs->stream;
 
   if (bs->is_tail) {
     gpr_mu_lock(&bs->slice_mu);
     size_t cur_length = bs->slices.length;
     gpr_mu_unlock(&bs->slice_mu);
     incoming_byte_stream_update_flow_control(
-        exec_ctx, transport_global, stream_global,
-        bs->next_action.max_size_hint, cur_length);
+        exec_ctx, t, s, bs->next_action.max_size_hint, cur_length);
   }
   gpr_mu_lock(&bs->slice_mu);
   if (bs->slices.count > 0) {
     *bs->next_action.slice = gpr_slice_buffer_take_first(&bs->slices);
-    grpc_exec_ctx_sched(exec_ctx, bs->next_action.on_complete, GRPC_ERROR_NONE,
-                        NULL);
+    grpc_closure_run(exec_ctx, bs->next_action.on_complete, GRPC_ERROR_NONE);
   } else if (bs->error != GRPC_ERROR_NONE) {
-    grpc_exec_ctx_sched(exec_ctx, bs->next_action.on_complete,
-                        GRPC_ERROR_REF(bs->error), NULL);
+    grpc_closure_run(exec_ctx, bs->next_action.on_complete,
+                     GRPC_ERROR_REF(bs->error));
   } else {
     bs->on_next = bs->next_action.on_complete;
     bs->next = bs->next_action.slice;
@@ -2186,8 +1964,8 @@
   bs->next_action.on_complete = on_complete;
   grpc_closure_init(&bs->next_action.closure, incoming_byte_stream_next_locked,
                     bs);
-  grpc_combiner_execute(exec_ctx, bs->transport->executor.combiner,
-                        &bs->next_action.closure, GRPC_ERROR_NONE);
+  grpc_combiner_execute(exec_ctx, bs->transport->combiner,
+                        &bs->next_action.closure, GRPC_ERROR_NONE, false);
   GPR_TIMER_END("incoming_byte_stream_next", 0);
   return 0;
 }
@@ -2200,8 +1978,7 @@
                                                 grpc_error *error_ignored) {
   grpc_chttp2_incoming_byte_stream *bs = byte_stream;
   GPR_ASSERT(bs->base.destroy == incoming_byte_stream_destroy);
-  decrement_active_streams_locked(exec_ctx, &bs->transport->global,
-                                  &bs->stream->global);
+  decrement_active_streams_locked(exec_ctx, bs->transport, bs->stream);
   incoming_byte_stream_unref(exec_ctx, bs);
 }
 
@@ -2212,84 +1989,86 @@
       (grpc_chttp2_incoming_byte_stream *)byte_stream;
   grpc_closure_init(&bs->destroy_action, incoming_byte_stream_destroy_locked,
                     bs);
-  grpc_combiner_execute(exec_ctx, bs->transport->executor.combiner,
-                        &bs->destroy_action, GRPC_ERROR_NONE);
+  grpc_combiner_execute(exec_ctx, bs->transport->combiner, &bs->destroy_action,
+                        GRPC_ERROR_NONE, false);
   GPR_TIMER_END("incoming_byte_stream_destroy", 0);
 }
 
-typedef struct {
-  grpc_chttp2_incoming_byte_stream *byte_stream;
-  gpr_slice slice;
-} incoming_byte_stream_push_arg;
+static void incoming_byte_stream_publish_error(
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs,
+    grpc_error *error) {
+  GPR_ASSERT(error != GRPC_ERROR_NONE);
+  grpc_exec_ctx_sched(exec_ctx, bs->on_next, GRPC_ERROR_REF(error), NULL);
+  bs->on_next = NULL;
+  GRPC_ERROR_UNREF(bs->error);
+  bs->error = error;
+}
 
 void grpc_chttp2_incoming_byte_stream_push(grpc_exec_ctx *exec_ctx,
                                            grpc_chttp2_incoming_byte_stream *bs,
                                            gpr_slice slice) {
   gpr_mu_lock(&bs->slice_mu);
-  if (bs->on_next != NULL) {
-    *bs->next = slice;
-    grpc_exec_ctx_sched(exec_ctx, bs->on_next, GRPC_ERROR_NONE, NULL);
-    bs->on_next = NULL;
+  if (bs->remaining_bytes < GPR_SLICE_LENGTH(slice)) {
+    incoming_byte_stream_publish_error(
+        exec_ctx, bs, GRPC_ERROR_CREATE("Too many bytes in stream"));
   } else {
-    gpr_slice_buffer_add(&bs->slices, slice);
+    bs->remaining_bytes -= (uint32_t)GPR_SLICE_LENGTH(slice);
+    if (bs->on_next != NULL) {
+      *bs->next = slice;
+      grpc_exec_ctx_sched(exec_ctx, bs->on_next, GRPC_ERROR_NONE, NULL);
+      bs->on_next = NULL;
+    } else {
+      gpr_slice_buffer_add(&bs->slices, slice);
+    }
   }
   gpr_mu_unlock(&bs->slice_mu);
 }
 
-static void incoming_byte_stream_finished_locked(grpc_exec_ctx *exec_ctx,
-                                                 void *bsp, grpc_error *error) {
-  grpc_chttp2_incoming_byte_stream *bs = bsp;
+void grpc_chttp2_incoming_byte_stream_finished(
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs,
+    grpc_error *error) {
+  if (error == GRPC_ERROR_NONE) {
+    gpr_mu_lock(&bs->slice_mu);
+    if (bs->remaining_bytes != 0) {
+      error = GRPC_ERROR_CREATE("Truncated message");
+    }
+    gpr_mu_unlock(&bs->slice_mu);
+  }
   if (error != GRPC_ERROR_NONE) {
-    grpc_exec_ctx_sched(exec_ctx, bs->on_next, GRPC_ERROR_REF(error), NULL);
-    bs->on_next = NULL;
-    GRPC_ERROR_UNREF(bs->error);
-    bs->error = error;
+    incoming_byte_stream_publish_error(exec_ctx, bs, error);
   }
   incoming_byte_stream_unref(exec_ctx, bs);
 }
 
-void grpc_chttp2_incoming_byte_stream_finished(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs,
-    grpc_error *error, int from_parsing_thread) {
-  GPR_TIMER_BEGIN("grpc_chttp2_incoming_byte_stream_finished", 0);
-  if (from_parsing_thread) {
-    grpc_closure_init(&bs->finished_action,
-                      incoming_byte_stream_finished_locked, bs);
-    grpc_combiner_execute(exec_ctx, bs->transport->executor.combiner,
-                          &bs->finished_action, GRPC_ERROR_REF(error));
-  } else {
-    incoming_byte_stream_finished_locked(exec_ctx, bs, error);
-  }
-  GPR_TIMER_END("grpc_chttp2_incoming_byte_stream_finished", 0);
-}
-
 grpc_chttp2_incoming_byte_stream *grpc_chttp2_incoming_byte_stream_create(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, uint32_t frame_size,
-    uint32_t flags, grpc_chttp2_incoming_frame_queue *add_to_queue) {
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t, grpc_chttp2_stream *s,
+    uint32_t frame_size, uint32_t flags) {
   grpc_chttp2_incoming_byte_stream *incoming_byte_stream =
       gpr_malloc(sizeof(*incoming_byte_stream));
   incoming_byte_stream->base.length = frame_size;
+  incoming_byte_stream->remaining_bytes = frame_size;
   incoming_byte_stream->base.flags = flags;
   incoming_byte_stream->base.next = incoming_byte_stream_next;
   incoming_byte_stream->base.destroy = incoming_byte_stream_destroy;
   gpr_mu_init(&incoming_byte_stream->slice_mu);
   gpr_ref_init(&incoming_byte_stream->refs, 2);
   incoming_byte_stream->next_message = NULL;
-  incoming_byte_stream->transport = TRANSPORT_FROM_PARSING(transport_parsing);
-  incoming_byte_stream->stream = STREAM_FROM_PARSING(stream_parsing);
-  gpr_ref(&incoming_byte_stream->stream->global.active_streams);
+  incoming_byte_stream->transport = t;
+  incoming_byte_stream->stream = s;
+  gpr_ref(&incoming_byte_stream->stream->active_streams);
   gpr_slice_buffer_init(&incoming_byte_stream->slices);
   incoming_byte_stream->on_next = NULL;
   incoming_byte_stream->is_tail = 1;
   incoming_byte_stream->error = GRPC_ERROR_NONE;
-  if (add_to_queue->head == NULL) {
-    add_to_queue->head = incoming_byte_stream;
+  grpc_chttp2_incoming_frame_queue *q = &s->incoming_frames;
+  if (q->head == NULL) {
+    q->head = incoming_byte_stream;
   } else {
-    add_to_queue->tail->is_tail = 0;
-    add_to_queue->tail->next_message = incoming_byte_stream;
+    q->tail->is_tail = 0;
+    q->tail->next_message = incoming_byte_stream;
   }
-  add_to_queue->tail = incoming_byte_stream;
+  q->tail = incoming_byte_stream;
+  grpc_chttp2_maybe_complete_recv_message(exec_ctx, t, s);
   return incoming_byte_stream;
 }
 
@@ -2298,67 +2077,49 @@
  */
 
 static char *format_flowctl_context_var(const char *context, const char *var,
-                                        int64_t val, uint32_t id,
-                                        char **scope) {
-  char *underscore_pos;
-  char *buf;
-  char *result;
+                                        int64_t val, uint32_t id) {
+  char *name;
   if (context == NULL) {
-    *scope = NULL;
-    gpr_asprintf(&buf, "%s(%" PRId64 ")", var, val);
-    result = gpr_leftpad(buf, ' ', 60);
-    gpr_free(buf);
-    return result;
+    name = gpr_strdup(var);
+  } else if (0 == strcmp(context, "t")) {
+    GPR_ASSERT(id == 0);
+    gpr_asprintf(&name, "TRANSPORT:%s", var);
+  } else if (0 == strcmp(context, "s")) {
+    GPR_ASSERT(id != 0);
+    gpr_asprintf(&name, "STREAM[%d]:%s", id, var);
+  } else {
+    gpr_asprintf(&name, "BAD_CONTEXT[%s][%d]:%s", context, id, var);
   }
-  underscore_pos = strchr(context, '_');
-  *scope = gpr_strdup(context);
-  (*scope)[underscore_pos - context] = 0;
-  if (id != 0) {
-    char *tmp = *scope;
-    gpr_asprintf(scope, "%s[%d]", tmp, id);
-    gpr_free(tmp);
-  }
-  gpr_asprintf(&buf, "%s.%s(%" PRId64 ")", underscore_pos + 1, var, val);
-  result = gpr_leftpad(buf, ' ', 60);
-  gpr_free(buf);
+  char *name_fld = gpr_leftpad(name, ' ', 64);
+  char *value;
+  gpr_asprintf(&value, "%" PRId64, val);
+  char *value_fld = gpr_leftpad(value, ' ', 8);
+  char *result;
+  gpr_asprintf(&result, "%s %s", name_fld, value_fld);
+  gpr_free(name);
+  gpr_free(name_fld);
+  gpr_free(value);
+  gpr_free(value_fld);
   return result;
 }
 
-static int samestr(char *a, char *b) {
-  if (a == NULL) {
-    return b == NULL;
-  }
-  if (b == NULL) {
-    return 0;
-  }
-  return 0 == strcmp(a, b);
-}
-
 void grpc_chttp2_flowctl_trace(const char *file, int line, const char *phase,
                                grpc_chttp2_flowctl_op op, const char *context1,
                                const char *var1, const char *context2,
                                const char *var2, int is_client,
                                uint32_t stream_id, int64_t val1, int64_t val2) {
-  char *scope1;
-  char *scope2;
   char *tmp_phase;
-  char *tmp_scope1;
-  char *label1 =
-      format_flowctl_context_var(context1, var1, val1, stream_id, &scope1);
-  char *label2 =
-      format_flowctl_context_var(context2, var2, val2, stream_id, &scope2);
+  char *label1 = format_flowctl_context_var(context1, var1, val1, stream_id);
+  char *label2 = format_flowctl_context_var(context2, var2, val2, stream_id);
   char *clisvr = is_client ? "client" : "server";
   char *prefix;
 
   tmp_phase = gpr_leftpad(phase, ' ', 8);
-  tmp_scope1 = gpr_leftpad(scope1, ' ', 11);
-  gpr_asprintf(&prefix, "FLOW %s: %s %s ", tmp_phase, clisvr, scope1);
+  gpr_asprintf(&prefix, "FLOW %s: %s ", tmp_phase, clisvr);
   gpr_free(tmp_phase);
-  gpr_free(tmp_scope1);
 
   switch (op) {
     case GRPC_CHTTP2_FLOWCTL_MOVE:
-      GPR_ASSERT(samestr(scope1, scope2));
       if (val2 != 0) {
         gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
                 "%sMOVE   %s <- %s giving %" PRId64, prefix, label1, label2,
@@ -2383,8 +2144,6 @@
       break;
   }
 
-  gpr_free(scope1);
-  gpr_free(scope2);
   gpr_free(label1);
   gpr_free(label2);
   gpr_free(prefix);
@@ -2421,10 +2180,11 @@
                                          grpc_transport *transport,
                                          gpr_slice_buffer *read_buffer) {
   grpc_chttp2_transport *t = (grpc_chttp2_transport *)transport;
-  REF_TRANSPORT(t, "reading_action"); /* matches unref inside reading_action */
+  GRPC_CHTTP2_REF_TRANSPORT(
+      t, "reading_action"); /* matches unref inside reading_action */
   if (read_buffer != NULL) {
     gpr_slice_buffer_move_into(read_buffer, &t->read_buffer);
     gpr_free(read_buffer);
   }
-  reading_action(exec_ctx, t, GRPC_ERROR_NONE);
+  read_action_begin(exec_ctx, t, GRPC_ERROR_NONE);
 }
diff --git a/src/core/ext/transport/chttp2/transport/frame.h b/src/core/ext/transport/chttp2/transport/frame.h
index 507aae4..1e444a9 100644
--- a/src/core/ext/transport/chttp2/transport/frame.h
+++ b/src/core/ext/transport/chttp2/transport/frame.h
@@ -40,8 +40,8 @@
 #include "src/core/lib/iomgr/error.h"
 
 /* defined in internal.h */
-typedef struct grpc_chttp2_stream_parsing grpc_chttp2_stream_parsing;
-typedef struct grpc_chttp2_transport_parsing grpc_chttp2_transport_parsing;
+typedef struct grpc_chttp2_stream grpc_chttp2_stream;
+typedef struct grpc_chttp2_transport grpc_chttp2_transport;
 
 #define GRPC_CHTTP2_FRAME_DATA 0
 #define GRPC_CHTTP2_FRAME_HEADER 1
diff --git a/src/core/ext/transport/chttp2/transport/frame_data.c b/src/core/ext/transport/chttp2/transport/frame_data.c
index 9046fbc..8668816 100644
--- a/src/core/ext/transport/chttp2/transport/frame_data.c
+++ b/src/core/ext/transport/chttp2/transport/frame_data.c
@@ -51,16 +51,11 @@
 
 void grpc_chttp2_data_parser_destroy(grpc_exec_ctx *exec_ctx,
                                      grpc_chttp2_data_parser *parser) {
-  grpc_byte_stream *bs;
-  if (parser->parsing_frame) {
+  if (parser->parsing_frame != NULL) {
     grpc_chttp2_incoming_byte_stream_finished(
-        exec_ctx, parser->parsing_frame, GRPC_ERROR_CREATE("Parser destroyed"),
-        1);
+        exec_ctx, parser->parsing_frame, GRPC_ERROR_CREATE("Parser destroyed"));
   }
-  while (
-      (bs = grpc_chttp2_incoming_frame_queue_pop(&parser->incoming_frames))) {
-    grpc_byte_stream_destroy(exec_ctx, bs);
-  }
+  GRPC_ERROR_UNREF(parser->error);
 }
 
 grpc_error *grpc_chttp2_data_parser_begin_frame(grpc_chttp2_data_parser *parser,
@@ -145,22 +140,17 @@
   stats->data_bytes += write_bytes;
 }
 
-grpc_error *grpc_chttp2_data_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
+static grpc_error *parse_inner(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_data_parser *p,
+                               grpc_chttp2_transport *t, grpc_chttp2_stream *s,
+                               gpr_slice slice) {
   uint8_t *const beg = GPR_SLICE_START_PTR(slice);
   uint8_t *const end = GPR_SLICE_END_PTR(slice);
   uint8_t *cur = beg;
-  grpc_chttp2_data_parser *p = parser;
   uint32_t message_flags;
   grpc_chttp2_incoming_byte_stream *incoming_byte_stream;
   char *msg;
 
-  if (is_last && p->is_last_frame) {
-    stream_parsing->received_close = 1;
-  }
-
   if (cur == end) {
     return GRPC_ERROR_NONE;
   }
@@ -171,7 +161,7 @@
       return GRPC_ERROR_REF(p->error);
     fh_0:
     case GRPC_CHTTP2_DATA_FH_0:
-      stream_parsing->stats.incoming.framing_bytes++;
+      s->stats.incoming.framing_bytes++;
       p->frame_type = *cur;
       switch (p->frame_type) {
         case 0:
@@ -184,7 +174,7 @@
           gpr_asprintf(&msg, "Bad GRPC frame type 0x%02x", p->frame_type);
           p->error = GRPC_ERROR_CREATE(msg);
           p->error = grpc_error_set_int(p->error, GRPC_ERROR_INT_STREAM_ID,
-                                        (intptr_t)stream_parsing->id);
+                                        (intptr_t)s->id);
           gpr_free(msg);
           msg = gpr_dump_slice(slice, GPR_DUMP_HEX | GPR_DUMP_ASCII);
           p->error =
@@ -201,7 +191,7 @@
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_1:
-      stream_parsing->stats.incoming.framing_bytes++;
+      s->stats.incoming.framing_bytes++;
       p->frame_size = ((uint32_t)*cur) << 24;
       if (++cur == end) {
         p->state = GRPC_CHTTP2_DATA_FH_2;
@@ -209,7 +199,7 @@
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_2:
-      stream_parsing->stats.incoming.framing_bytes++;
+      s->stats.incoming.framing_bytes++;
       p->frame_size |= ((uint32_t)*cur) << 16;
       if (++cur == end) {
         p->state = GRPC_CHTTP2_DATA_FH_3;
@@ -217,7 +207,7 @@
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_3:
-      stream_parsing->stats.incoming.framing_bytes++;
+      s->stats.incoming.framing_bytes++;
       p->frame_size |= ((uint32_t)*cur) << 8;
       if (++cur == end) {
         p->state = GRPC_CHTTP2_DATA_FH_4;
@@ -225,7 +215,7 @@
       }
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FH_4:
-      stream_parsing->stats.incoming.framing_bytes++;
+      s->stats.incoming.framing_bytes++;
       p->frame_size |= ((uint32_t)*cur);
       p->state = GRPC_CHTTP2_DATA_FRAME;
       ++cur;
@@ -234,35 +224,32 @@
         message_flags |= GRPC_WRITE_INTERNAL_COMPRESS;
       }
       p->parsing_frame = incoming_byte_stream =
-          grpc_chttp2_incoming_byte_stream_create(
-              exec_ctx, transport_parsing, stream_parsing, p->frame_size,
-              message_flags, &p->incoming_frames);
+          grpc_chttp2_incoming_byte_stream_create(exec_ctx, t, s, p->frame_size,
+                                                  message_flags);
     /* fallthrough */
     case GRPC_CHTTP2_DATA_FRAME:
-      grpc_chttp2_list_add_parsing_seen_stream(transport_parsing,
-                                               stream_parsing);
       if (cur == end) {
         return GRPC_ERROR_NONE;
       }
       uint32_t remaining = (uint32_t)(end - cur);
       if (remaining == p->frame_size) {
-        stream_parsing->stats.incoming.data_bytes += p->frame_size;
+        s->stats.incoming.data_bytes += p->frame_size;
         grpc_chttp2_incoming_byte_stream_push(
             exec_ctx, p->parsing_frame,
             gpr_slice_sub(slice, (size_t)(cur - beg), (size_t)(end - beg)));
         grpc_chttp2_incoming_byte_stream_finished(exec_ctx, p->parsing_frame,
-                                                  GRPC_ERROR_NONE, 1);
+                                                  GRPC_ERROR_NONE);
         p->parsing_frame = NULL;
         p->state = GRPC_CHTTP2_DATA_FH_0;
         return GRPC_ERROR_NONE;
       } else if (remaining > p->frame_size) {
-        stream_parsing->stats.incoming.data_bytes += p->frame_size;
+        s->stats.incoming.data_bytes += p->frame_size;
         grpc_chttp2_incoming_byte_stream_push(
             exec_ctx, p->parsing_frame,
             gpr_slice_sub(slice, (size_t)(cur - beg),
                           (size_t)(cur + p->frame_size - beg)));
         grpc_chttp2_incoming_byte_stream_finished(exec_ctx, p->parsing_frame,
-                                                  GRPC_ERROR_NONE, 1);
+                                                  GRPC_ERROR_NONE);
         p->parsing_frame = NULL;
         cur += p->frame_size;
         goto fh_0; /* loop */
@@ -272,10 +259,25 @@
             exec_ctx, p->parsing_frame,
             gpr_slice_sub(slice, (size_t)(cur - beg), (size_t)(end - beg)));
         p->frame_size -= remaining;
-        stream_parsing->stats.incoming.data_bytes += remaining;
+        s->stats.incoming.data_bytes += remaining;
         return GRPC_ERROR_NONE;
       }
   }
 
   GPR_UNREACHABLE_CODE(return GRPC_ERROR_CREATE("Should never reach here"));
 }
+
+grpc_error *grpc_chttp2_data_parser_parse(grpc_exec_ctx *exec_ctx, void *parser,
+                                          grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s,
+                                          gpr_slice slice, int is_last) {
+  grpc_chttp2_data_parser *p = parser;
+  grpc_error *error = parse_inner(exec_ctx, p, t, s, slice);
+
+  if (is_last && p->is_last_frame) {
+    grpc_chttp2_mark_stream_closed(exec_ctx, t, s, true, false,
+                                   GRPC_ERROR_NONE);
+  }
+
+  return error;
+}
diff --git a/src/core/ext/transport/chttp2/transport/frame_data.h b/src/core/ext/transport/chttp2/transport/frame_data.h
index a21a794..eb2d97d 100644
--- a/src/core/ext/transport/chttp2/transport/frame_data.h
+++ b/src/core/ext/transport/chttp2/transport/frame_data.h
@@ -69,7 +69,6 @@
   grpc_error *error;
 
   int is_frame_compressed;
-  grpc_chttp2_incoming_frame_queue incoming_frames;
   grpc_chttp2_incoming_byte_stream *parsing_frame;
 } grpc_chttp2_data_parser;
 
@@ -92,10 +91,10 @@
 
 /* handle a slice of a data frame - is_last indicates the last slice of a
    frame */
-grpc_error *grpc_chttp2_data_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
+grpc_error *grpc_chttp2_data_parser_parse(grpc_exec_ctx *exec_ctx, void *parser,
+                                          grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s,
+                                          gpr_slice slice, int is_last);
 
 void grpc_chttp2_encode_data(uint32_t id, gpr_slice_buffer *inbuf,
                              uint32_t write_bytes, int is_eof,
diff --git a/src/core/ext/transport/chttp2/transport/frame_goaway.c b/src/core/ext/transport/chttp2/transport/frame_goaway.c
index 299e27a..33d2269 100644
--- a/src/core/ext/transport/chttp2/transport/frame_goaway.c
+++ b/src/core/ext/transport/chttp2/transport/frame_goaway.c
@@ -67,10 +67,11 @@
   return GRPC_ERROR_NONE;
 }
 
-grpc_error *grpc_chttp2_goaway_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
+grpc_error *grpc_chttp2_goaway_parser_parse(grpc_exec_ctx *exec_ctx,
+                                            void *parser,
+                                            grpc_chttp2_transport *t,
+                                            grpc_chttp2_stream *s,
+                                            gpr_slice slice, int is_last) {
   uint8_t *const beg = GPR_SLICE_START_PTR(slice);
   uint8_t *const end = GPR_SLICE_END_PTR(slice);
   uint8_t *cur = beg;
@@ -148,12 +149,9 @@
       p->debug_pos += (uint32_t)(end - cur);
       p->state = GRPC_CHTTP2_GOAWAY_DEBUG;
       if (is_last) {
-        transport_parsing->goaway_received = 1;
-        transport_parsing->goaway_last_stream_index = p->last_stream_id;
-        gpr_slice_unref(transport_parsing->goaway_text);
-        transport_parsing->goaway_error = (grpc_status_code)p->error_code;
-        transport_parsing->goaway_text =
-            gpr_slice_new(p->debug_data, p->debug_length, gpr_free);
+        grpc_chttp2_add_incoming_goaway(
+            exec_ctx, t, (uint32_t)p->error_code,
+            gpr_slice_new(p->debug_data, p->debug_length, gpr_free));
         p->debug_data = NULL;
       }
       return GRPC_ERROR_NONE;
diff --git a/src/core/ext/transport/chttp2/transport/frame_goaway.h b/src/core/ext/transport/chttp2/transport/frame_goaway.h
index eb43034..355104a 100644
--- a/src/core/ext/transport/chttp2/transport/frame_goaway.h
+++ b/src/core/ext/transport/chttp2/transport/frame_goaway.h
@@ -65,10 +65,11 @@
 void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p);
 grpc_error *grpc_chttp2_goaway_parser_begin_frame(
     grpc_chttp2_goaway_parser *parser, uint32_t length, uint8_t flags);
-grpc_error *grpc_chttp2_goaway_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
+grpc_error *grpc_chttp2_goaway_parser_parse(grpc_exec_ctx *exec_ctx,
+                                            void *parser,
+                                            grpc_chttp2_transport *t,
+                                            grpc_chttp2_stream *s,
+                                            gpr_slice slice, int is_last);
 
 void grpc_chttp2_goaway_append(uint32_t last_stream_id, uint32_t error_code,
                                gpr_slice debug_data,
diff --git a/src/core/ext/transport/chttp2/transport/frame_ping.c b/src/core/ext/transport/chttp2/transport/frame_ping.c
index 1f814ab..624f426 100644
--- a/src/core/ext/transport/chttp2/transport/frame_ping.c
+++ b/src/core/ext/transport/chttp2/transport/frame_ping.c
@@ -73,10 +73,10 @@
   return GRPC_ERROR_NONE;
 }
 
-grpc_error *grpc_chttp2_ping_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
+grpc_error *grpc_chttp2_ping_parser_parse(grpc_exec_ctx *exec_ctx, void *parser,
+                                          grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s,
+                                          gpr_slice slice, int is_last) {
   uint8_t *const beg = GPR_SLICE_START_PTR(slice);
   uint8_t *const end = GPR_SLICE_END_PTR(slice);
   uint8_t *cur = beg;
@@ -91,10 +91,11 @@
   if (p->byte == 8) {
     GPR_ASSERT(is_last);
     if (p->is_ack) {
-      grpc_chttp2_ack_ping(exec_ctx, transport_parsing, p->opaque_8bytes);
+      grpc_chttp2_ack_ping(exec_ctx, t, p->opaque_8bytes);
     } else {
-      gpr_slice_buffer_add(&transport_parsing->qbuf,
+      gpr_slice_buffer_add(&t->qbuf,
                            grpc_chttp2_ping_create(1, p->opaque_8bytes));
+      grpc_chttp2_initiate_write(exec_ctx, t, false, "ping response");
     }
   }
 
diff --git a/src/core/ext/transport/chttp2/transport/frame_ping.h b/src/core/ext/transport/chttp2/transport/frame_ping.h
index 5a87234..2071f64 100644
--- a/src/core/ext/transport/chttp2/transport/frame_ping.h
+++ b/src/core/ext/transport/chttp2/transport/frame_ping.h
@@ -48,9 +48,9 @@
 
 grpc_error *grpc_chttp2_ping_parser_begin_frame(grpc_chttp2_ping_parser *parser,
                                                 uint32_t length, uint8_t flags);
-grpc_error *grpc_chttp2_ping_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
+grpc_error *grpc_chttp2_ping_parser_parse(grpc_exec_ctx *exec_ctx, void *parser,
+                                          grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s,
+                                          gpr_slice slice, int is_last);
 
 #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FRAME_PING_H */
diff --git a/src/core/ext/transport/chttp2/transport/frame_rst_stream.c b/src/core/ext/transport/chttp2/transport/frame_rst_stream.c
index e3a3c9e..9eac050 100644
--- a/src/core/ext/transport/chttp2/transport/frame_rst_stream.c
+++ b/src/core/ext/transport/chttp2/transport/frame_rst_stream.c
@@ -39,6 +39,8 @@
 #include <grpc/support/string_util.h>
 
 #include "src/core/ext/transport/chttp2/transport/frame.h"
+#include "src/core/ext/transport/chttp2/transport/http2_errors.h"
+#include "src/core/ext/transport/chttp2/transport/status_conversion.h"
 
 gpr_slice grpc_chttp2_rst_stream_create(uint32_t id, uint32_t code,
                                         grpc_transport_one_way_stats *stats) {
@@ -83,10 +85,11 @@
   return GRPC_ERROR_NONE;
 }
 
-grpc_error *grpc_chttp2_rst_stream_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
+grpc_error *grpc_chttp2_rst_stream_parser_parse(grpc_exec_ctx *exec_ctx,
+                                                void *parser,
+                                                grpc_chttp2_transport *t,
+                                                grpc_chttp2_stream *s,
+                                                gpr_slice slice, int is_last) {
   uint8_t *const beg = GPR_SLICE_START_PTR(slice);
   uint8_t *const end = GPR_SLICE_END_PTR(slice);
   uint8_t *cur = beg;
@@ -97,19 +100,28 @@
     cur++;
     p->byte++;
   }
-  stream_parsing->stats.incoming.framing_bytes += (uint64_t)(end - cur);
+  s->stats.incoming.framing_bytes += (uint64_t)(end - cur);
 
   if (p->byte == 4) {
     GPR_ASSERT(is_last);
-    stream_parsing->received_close = 1;
-    if (stream_parsing->forced_close_error == GRPC_ERROR_NONE) {
-      stream_parsing->forced_close_error = grpc_error_set_int(
-          GRPC_ERROR_CREATE("RST_STREAM"), GRPC_ERROR_INT_HTTP2_ERROR,
-          (intptr_t)((((uint32_t)p->reason_bytes[0]) << 24) |
-                     (((uint32_t)p->reason_bytes[1]) << 16) |
-                     (((uint32_t)p->reason_bytes[2]) << 8) |
-                     (((uint32_t)p->reason_bytes[3]))));
+    uint32_t reason = (((uint32_t)p->reason_bytes[0]) << 24) |
+                      (((uint32_t)p->reason_bytes[1]) << 16) |
+                      (((uint32_t)p->reason_bytes[2]) << 8) |
+                      (((uint32_t)p->reason_bytes[3]));
+    grpc_error *error = GRPC_ERROR_NONE;
+    if (reason != GRPC_CHTTP2_NO_ERROR) {
+      error = grpc_error_set_int(GRPC_ERROR_CREATE("RST_STREAM"),
+                                 GRPC_ERROR_INT_HTTP2_ERROR, (intptr_t)reason);
+      grpc_status_code status_code = grpc_chttp2_http2_error_to_grpc_status(
+          (grpc_chttp2_error_code)reason, s->deadline);
+      char *status_details;
+      gpr_asprintf(&status_details, "Received RST_STREAM with error code %d",
+                   reason);
+      gpr_slice slice_details = gpr_slice_from_copied_string(status_details);
+      gpr_free(status_details);
+      grpc_chttp2_fake_status(exec_ctx, t, s, status_code, &slice_details);
     }
+    grpc_chttp2_mark_stream_closed(exec_ctx, t, s, true, true, error);
   }
 
   return GRPC_ERROR_NONE;
diff --git a/src/core/ext/transport/chttp2/transport/frame_rst_stream.h b/src/core/ext/transport/chttp2/transport/frame_rst_stream.h
index 11cf94f..5a1f578 100644
--- a/src/core/ext/transport/chttp2/transport/frame_rst_stream.h
+++ b/src/core/ext/transport/chttp2/transport/frame_rst_stream.h
@@ -49,9 +49,10 @@
 
 grpc_error *grpc_chttp2_rst_stream_parser_begin_frame(
     grpc_chttp2_rst_stream_parser *parser, uint32_t length, uint8_t flags);
-grpc_error *grpc_chttp2_rst_stream_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
+grpc_error *grpc_chttp2_rst_stream_parser_parse(grpc_exec_ctx *exec_ctx,
+                                                void *parser,
+                                                grpc_chttp2_transport *t,
+                                                grpc_chttp2_stream *s,
+                                                gpr_slice slice, int is_last);
 
 #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FRAME_RST_STREAM_H */
diff --git a/src/core/ext/transport/chttp2/transport/frame_settings.c b/src/core/ext/transport/chttp2/transport/frame_settings.c
index 04b96c4..92022f9 100644
--- a/src/core/ext/transport/chttp2/transport/frame_settings.c
+++ b/src/core/ext/transport/chttp2/transport/frame_settings.c
@@ -143,10 +143,10 @@
   }
 }
 
-grpc_error *grpc_chttp2_settings_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *p,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
+grpc_error *grpc_chttp2_settings_parser_parse(grpc_exec_ctx *exec_ctx, void *p,
+                                              grpc_chttp2_transport *t,
+                                              grpc_chttp2_stream *s,
+                                              gpr_slice slice, int is_last) {
   grpc_chttp2_settings_parser *parser = p;
   const uint8_t *cur = GPR_SLICE_START_PTR(slice);
   const uint8_t *end = GPR_SLICE_END_PTR(slice);
@@ -162,11 +162,9 @@
         if (cur == end) {
           parser->state = GRPC_CHTTP2_SPS_ID0;
           if (is_last) {
-            transport_parsing->settings_updated = 1;
             memcpy(parser->target_settings, parser->incoming_settings,
                    GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t));
-            gpr_slice_buffer_add(&transport_parsing->qbuf,
-                                 grpc_chttp2_settings_ack_create());
+            gpr_slice_buffer_add(&t->qbuf, grpc_chttp2_settings_ack_create());
           }
           return GRPC_ERROR_NONE;
         }
@@ -226,9 +224,9 @@
                 break;
               case GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE:
                 grpc_chttp2_goaway_append(
-                    transport_parsing->last_incoming_stream_id, sp->error_value,
+                    t->last_new_stream_id, sp->error_value,
                     gpr_slice_from_static_string("HTTP2 settings error"),
-                    &transport_parsing->qbuf);
+                    &t->qbuf);
                 gpr_asprintf(&msg, "invalid value %u passed for %s",
                              parser->value, sp->name);
                 grpc_error *err = GRPC_ERROR_CREATE(msg);
@@ -238,18 +236,17 @@
           }
           if (parser->id == GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE &&
               parser->incoming_settings[parser->id] != parser->value) {
-            transport_parsing->initial_window_update =
+            t->initial_window_update =
                 (int64_t)parser->value - parser->incoming_settings[parser->id];
             if (grpc_http_trace) {
               gpr_log(GPR_DEBUG, "adding %d for initial_window change",
-                      (int)transport_parsing->initial_window_update);
+                      (int)t->initial_window_update);
             }
           }
           parser->incoming_settings[parser->id] = parser->value;
           if (grpc_http_trace) {
             gpr_log(GPR_DEBUG, "CHTTP2:%s: got setting %d = %d",
-                    transport_parsing->is_client ? "CLI" : "SVR", parser->id,
-                    parser->value);
+                    t->is_client ? "CLI" : "SVR", parser->id, parser->value);
           }
         } else if (grpc_http_trace) {
           gpr_log(GPR_ERROR, "CHTTP2: Ignoring unknown setting %d (value %d)",
diff --git a/src/core/ext/transport/chttp2/transport/frame_settings.h b/src/core/ext/transport/chttp2/transport/frame_settings.h
index f654c59..4bfa944 100644
--- a/src/core/ext/transport/chttp2/transport/frame_settings.h
+++ b/src/core/ext/transport/chttp2/transport/frame_settings.h
@@ -95,9 +95,10 @@
 grpc_error *grpc_chttp2_settings_parser_begin_frame(
     grpc_chttp2_settings_parser *parser, uint32_t length, uint8_t flags,
     uint32_t *settings);
-grpc_error *grpc_chttp2_settings_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
+grpc_error *grpc_chttp2_settings_parser_parse(grpc_exec_ctx *exec_ctx,
+                                              void *parser,
+                                              grpc_chttp2_transport *t,
+                                              grpc_chttp2_stream *s,
+                                              gpr_slice slice, int is_last);
 
 #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FRAME_SETTINGS_H */
diff --git a/src/core/ext/transport/chttp2/transport/frame_window_update.c b/src/core/ext/transport/chttp2/transport/frame_window_update.c
index 3cf848f..418166a 100644
--- a/src/core/ext/transport/chttp2/transport/frame_window_update.c
+++ b/src/core/ext/transport/chttp2/transport/frame_window_update.c
@@ -80,9 +80,8 @@
 }
 
 grpc_error *grpc_chttp2_window_update_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
+    grpc_exec_ctx *exec_ctx, void *parser, grpc_chttp2_transport *t,
+    grpc_chttp2_stream *s, gpr_slice slice, int is_last) {
   uint8_t *const beg = GPR_SLICE_START_PTR(slice);
   uint8_t *const end = GPR_SLICE_END_PTR(slice);
   uint8_t *cur = beg;
@@ -94,8 +93,8 @@
     p->byte++;
   }
 
-  if (stream_parsing != NULL) {
-    stream_parsing->stats.incoming.framing_bytes += (uint32_t)(end - cur);
+  if (s != NULL) {
+    s->stats.incoming.framing_bytes += (uint32_t)(end - cur);
   }
 
   if (p->byte == 4) {
@@ -109,17 +108,26 @@
     }
     GPR_ASSERT(is_last);
 
-    if (transport_parsing->incoming_stream_id != 0) {
-      if (stream_parsing != NULL) {
-        GRPC_CHTTP2_FLOW_CREDIT_STREAM("parse", transport_parsing,
-                                       stream_parsing, outgoing_window,
+    if (t->incoming_stream_id != 0) {
+      if (s != NULL) {
+        bool was_zero = s->outgoing_window <= 0;
+        GRPC_CHTTP2_FLOW_CREDIT_STREAM("parse", t, s, outgoing_window,
                                        received_update);
-        grpc_chttp2_list_add_parsing_seen_stream(transport_parsing,
-                                                 stream_parsing);
+        bool is_zero = s->outgoing_window <= 0;
+        if (was_zero && !is_zero) {
+          grpc_chttp2_become_writable(exec_ctx, t, s, false,
+                                      "stream.read_flow_control");
+        }
       }
     } else {
-      GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parse", transport_parsing,
-                                        outgoing_window, received_update);
+      bool was_zero = t->outgoing_window <= 0;
+      GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parse", t, outgoing_window,
+                                        received_update);
+      bool is_zero = t->outgoing_window <= 0;
+      if (was_zero && !is_zero) {
+        grpc_chttp2_initiate_write(exec_ctx, t, false,
+                                   "new_global_flow_control");
+      }
     }
   }
 
diff --git a/src/core/ext/transport/chttp2/transport/frame_window_update.h b/src/core/ext/transport/chttp2/transport/frame_window_update.h
index 1bcbbf9..6e62f31 100644
--- a/src/core/ext/transport/chttp2/transport/frame_window_update.h
+++ b/src/core/ext/transport/chttp2/transport/frame_window_update.h
@@ -51,8 +51,7 @@
 grpc_error *grpc_chttp2_window_update_parser_begin_frame(
     grpc_chttp2_window_update_parser *parser, uint32_t length, uint8_t flags);
 grpc_error *grpc_chttp2_window_update_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
+    grpc_exec_ctx *exec_ctx, void *parser, grpc_chttp2_transport *t,
+    grpc_chttp2_stream *s, gpr_slice slice, int is_last);
 
 #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FRAME_WINDOW_UPDATE_H */
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.c b/src/core/ext/transport/chttp2/transport/hpack_parser.c
index 522455f..8180f78 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.c
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.c
@@ -78,69 +78,96 @@
    a set of indirect jumps, and so not waste stack space. */
 
 /* forward declarations for parsing states */
-static grpc_error *parse_begin(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_begin(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                const uint8_t *end);
-static grpc_error *parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_error(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                const uint8_t *end, grpc_error *error);
-static grpc_error *still_parse_error(grpc_chttp2_hpack_parser *p,
+static grpc_error *still_parse_error(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_hpack_parser *p,
                                      const uint8_t *cur, const uint8_t *end);
-static grpc_error *parse_illegal_op(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_illegal_op(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_hpack_parser *p,
                                     const uint8_t *cur, const uint8_t *end);
 
-static grpc_error *parse_string_prefix(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_string_prefix(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end);
-static grpc_error *parse_key_string(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_key_string(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_hpack_parser *p,
                                     const uint8_t *cur, const uint8_t *end);
 static grpc_error *parse_value_string_with_indexed_key(
-    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end);
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+    const uint8_t *end);
 static grpc_error *parse_value_string_with_literal_key(
-    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end);
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+    const uint8_t *end);
 
-static grpc_error *parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value0(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end);
-static grpc_error *parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value1(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end);
-static grpc_error *parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value2(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end);
-static grpc_error *parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value3(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end);
-static grpc_error *parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value4(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end);
-static grpc_error *parse_value5up(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_value5up(grpc_exec_ctx *exec_ctx,
+                                  grpc_chttp2_hpack_parser *p,
                                   const uint8_t *cur, const uint8_t *end);
 
-static grpc_error *parse_indexed_field(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_indexed_field(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end);
-static grpc_error *parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_indexed_field_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end);
-static grpc_error *parse_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_incidx(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end);
-static grpc_error *parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_incidx_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end);
-static grpc_error *parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_incidx_v(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end);
-static grpc_error *parse_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_notidx(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end);
-static grpc_error *parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_notidx_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end);
-static grpc_error *parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_notidx_v(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end);
-static grpc_error *parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_nvridx(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end);
-static grpc_error *parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_nvridx_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end);
-static grpc_error *parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_nvridx_v(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end);
-static grpc_error *parse_max_tbl_size(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_max_tbl_size(grpc_exec_ctx *exec_ctx,
+                                      grpc_chttp2_hpack_parser *p,
                                       const uint8_t *cur, const uint8_t *end);
-static grpc_error *parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_max_tbl_size_x(grpc_exec_ctx *exec_ctx,
+                                        grpc_chttp2_hpack_parser *p,
                                         const uint8_t *cur, const uint8_t *end);
 
 /* we translate the first byte of a hpack field into one of these decoding
@@ -639,8 +666,8 @@
 };
 
 /* emission helpers */
-static grpc_error *on_hdr(grpc_chttp2_hpack_parser *p, grpc_mdelem *md,
-                          int add_to_table) {
+static grpc_error *on_hdr(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_parser *p,
+                          grpc_mdelem *md, int add_to_table) {
   if (add_to_table) {
     grpc_error *err = grpc_chttp2_hptbl_add(&p->table, md);
     if (err != GRPC_ERROR_NONE) return err;
@@ -649,7 +676,7 @@
     GRPC_MDELEM_UNREF(md);
     return GRPC_ERROR_CREATE("on_header callback not set");
   }
-  p->on_header(p->on_header_user_data, md);
+  p->on_header(exec_ctx, p->on_header_user_data, md);
   return GRPC_ERROR_NONE;
 }
 
@@ -661,78 +688,86 @@
 }
 
 /* jump to the next state */
-static grpc_error *parse_next(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_next(grpc_exec_ctx *exec_ctx,
+                              grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                               const uint8_t *end) {
   p->state = *p->next_state++;
-  return p->state(p, cur, end);
+  return p->state(exec_ctx, p, cur, end);
 }
 
 /* begin parsing a header: all functionality is encoded into lookup tables
    above */
-static grpc_error *parse_begin(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_begin(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                const uint8_t *end) {
   if (cur == end) {
     p->state = parse_begin;
     return GRPC_ERROR_NONE;
   }
 
-  return first_byte_action[first_byte_lut[*cur]](p, cur, end);
+  return first_byte_action[first_byte_lut[*cur]](exec_ctx, p, cur, end);
 }
 
 /* stream dependency and prioritization data: we just skip it */
-static grpc_error *parse_stream_weight(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_stream_weight(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_weight;
     return GRPC_ERROR_NONE;
   }
 
-  return p->after_prioritization(p, cur + 1, end);
+  return p->after_prioritization(exec_ctx, p, cur + 1, end);
 }
 
-static grpc_error *parse_stream_dep3(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_stream_dep3(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_hpack_parser *p,
                                      const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep3;
     return GRPC_ERROR_NONE;
   }
 
-  return parse_stream_weight(p, cur + 1, end);
+  return parse_stream_weight(exec_ctx, p, cur + 1, end);
 }
 
-static grpc_error *parse_stream_dep2(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_stream_dep2(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_hpack_parser *p,
                                      const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep2;
     return GRPC_ERROR_NONE;
   }
 
-  return parse_stream_dep3(p, cur + 1, end);
+  return parse_stream_dep3(exec_ctx, p, cur + 1, end);
 }
 
-static grpc_error *parse_stream_dep1(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_stream_dep1(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_hpack_parser *p,
                                      const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep1;
     return GRPC_ERROR_NONE;
   }
 
-  return parse_stream_dep2(p, cur + 1, end);
+  return parse_stream_dep2(exec_ctx, p, cur + 1, end);
 }
 
-static grpc_error *parse_stream_dep0(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_stream_dep0(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_hpack_parser *p,
                                      const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_stream_dep0;
     return GRPC_ERROR_NONE;
   }
 
-  return parse_stream_dep1(p, cur + 1, end);
+  return parse_stream_dep1(exec_ctx, p, cur + 1, end);
 }
 
 /* emit an indexed field; for now just logs it to console; jumps to
    begin the next field on completion */
-static grpc_error *finish_indexed_field(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_indexed_field(grpc_exec_ctx *exec_ctx,
+                                        grpc_chttp2_hpack_parser *p,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
@@ -743,21 +778,23 @@
         GRPC_ERROR_INT_SIZE, (intptr_t)p->table.num_ents);
   }
   GRPC_MDELEM_REF(md);
-  grpc_error *err = on_hdr(p, md, 0);
+  grpc_error *err = on_hdr(exec_ctx, p, md, 0);
   if (err != GRPC_ERROR_NONE) return err;
-  return parse_begin(p, cur, end);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* parse an indexed field with index < 127 */
-static grpc_error *parse_indexed_field(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_indexed_field(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end) {
   p->dynamic_table_update_allowed = 0;
   p->index = (*cur) & 0x7f;
-  return finish_indexed_field(p, cur + 1, end);
+  return finish_indexed_field(exec_ctx, p, cur + 1, end);
 }
 
 /* parse an indexed field with index >= 127 */
-static grpc_error *parse_indexed_field_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_indexed_field_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
@@ -766,49 +803,53 @@
   p->next_state = and_then;
   p->index = 0x7f;
   p->parsing.value = &p->index;
-  return parse_value0(p, cur + 1, end);
+  return parse_value0(exec_ctx, p, cur + 1, end);
 }
 
 /* finish a literal header with incremental indexing: just log, and jump to '
    begin */
-static grpc_error *finish_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_lithdr_incidx(grpc_exec_ctx *exec_ctx,
+                                        grpc_chttp2_hpack_parser *p,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   GPR_ASSERT(md != NULL); /* handled in string parsing */
-  grpc_error *err =
-      on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
-                                                  take_string(p, &p->value)),
-             1);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_begin(p, cur, end);
+  grpc_error *err = on_hdr(
+      exec_ctx, p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
+                                                     take_string(p, &p->value)),
+      1);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* finish a literal header with incremental indexing with no index */
-static grpc_error *finish_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_lithdr_incidx_v(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_hpack_parser *p,
                                           const uint8_t *cur,
                                           const uint8_t *end) {
-  grpc_error *err =
-      on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
-                                                  take_string(p, &p->value)),
-             1);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_begin(p, cur, end);
+  grpc_error *err = on_hdr(
+      exec_ctx, p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
+                                                     take_string(p, &p->value)),
+      1);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* parse a literal header with incremental indexing; index < 63 */
-static grpc_error *parse_lithdr_incidx(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_incidx(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_value_string_with_indexed_key, finish_lithdr_incidx};
   p->dynamic_table_update_allowed = 0;
   p->next_state = and_then;
   p->index = (*cur) & 0x3f;
-  return parse_string_prefix(p, cur + 1, end);
+  return parse_string_prefix(exec_ctx, p, cur + 1, end);
 }
 
 /* parse a literal header with incremental indexing; index >= 63 */
-static grpc_error *parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_incidx_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
@@ -818,11 +859,12 @@
   p->next_state = and_then;
   p->index = 0x3f;
   p->parsing.value = &p->index;
-  return parse_value0(p, cur + 1, end);
+  return parse_value0(exec_ctx, p, cur + 1, end);
 }
 
 /* parse a literal header with incremental indexing; index = 0 */
-static grpc_error *parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_incidx_v(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
@@ -830,48 +872,52 @@
       parse_value_string_with_literal_key, finish_lithdr_incidx_v};
   p->dynamic_table_update_allowed = 0;
   p->next_state = and_then;
-  return parse_string_prefix(p, cur + 1, end);
+  return parse_string_prefix(exec_ctx, p, cur + 1, end);
 }
 
 /* finish a literal header without incremental indexing */
-static grpc_error *finish_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_lithdr_notidx(grpc_exec_ctx *exec_ctx,
+                                        grpc_chttp2_hpack_parser *p,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   GPR_ASSERT(md != NULL); /* handled in string parsing */
-  grpc_error *err =
-      on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
-                                                  take_string(p, &p->value)),
-             0);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_begin(p, cur, end);
+  grpc_error *err = on_hdr(
+      exec_ctx, p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
+                                                     take_string(p, &p->value)),
+      0);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* finish a literal header without incremental indexing with index = 0 */
-static grpc_error *finish_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_lithdr_notidx_v(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_hpack_parser *p,
                                           const uint8_t *cur,
                                           const uint8_t *end) {
-  grpc_error *err =
-      on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
-                                                  take_string(p, &p->value)),
-             0);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_begin(p, cur, end);
+  grpc_error *err = on_hdr(
+      exec_ctx, p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
+                                                     take_string(p, &p->value)),
+      0);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* parse a literal header without incremental indexing; index < 15 */
-static grpc_error *parse_lithdr_notidx(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_notidx(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_value_string_with_indexed_key, finish_lithdr_notidx};
   p->dynamic_table_update_allowed = 0;
   p->next_state = and_then;
   p->index = (*cur) & 0xf;
-  return parse_string_prefix(p, cur + 1, end);
+  return parse_string_prefix(exec_ctx, p, cur + 1, end);
 }
 
 /* parse a literal header without incremental indexing; index >= 15 */
-static grpc_error *parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_notidx_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
@@ -881,11 +927,12 @@
   p->next_state = and_then;
   p->index = 0xf;
   p->parsing.value = &p->index;
-  return parse_value0(p, cur + 1, end);
+  return parse_value0(exec_ctx, p, cur + 1, end);
 }
 
 /* parse a literal header without incremental indexing; index == 0 */
-static grpc_error *parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_notidx_v(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
@@ -893,48 +940,52 @@
       parse_value_string_with_literal_key, finish_lithdr_notidx_v};
   p->dynamic_table_update_allowed = 0;
   p->next_state = and_then;
-  return parse_string_prefix(p, cur + 1, end);
+  return parse_string_prefix(exec_ctx, p, cur + 1, end);
 }
 
 /* finish a literal header that is never indexed */
-static grpc_error *finish_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_lithdr_nvridx(grpc_exec_ctx *exec_ctx,
+                                        grpc_chttp2_hpack_parser *p,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index);
   GPR_ASSERT(md != NULL); /* handled in string parsing */
-  grpc_error *err =
-      on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
-                                                  take_string(p, &p->value)),
-             0);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_begin(p, cur, end);
+  grpc_error *err = on_hdr(
+      exec_ctx, p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key),
+                                                     take_string(p, &p->value)),
+      0);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* finish a literal header that is never indexed with an extra value */
-static grpc_error *finish_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_lithdr_nvridx_v(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_hpack_parser *p,
                                           const uint8_t *cur,
                                           const uint8_t *end) {
-  grpc_error *err =
-      on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
-                                                  take_string(p, &p->value)),
-             0);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_begin(p, cur, end);
+  grpc_error *err = on_hdr(
+      exec_ctx, p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key),
+                                                     take_string(p, &p->value)),
+      0);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* parse a literal header that is never indexed; index < 15 */
-static grpc_error *parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_nvridx(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       parse_value_string_with_indexed_key, finish_lithdr_nvridx};
   p->dynamic_table_update_allowed = 0;
   p->next_state = and_then;
   p->index = (*cur) & 0xf;
-  return parse_string_prefix(p, cur + 1, end);
+  return parse_string_prefix(exec_ctx, p, cur + 1, end);
 }
 
 /* parse a literal header that is never indexed; index >= 15 */
-static grpc_error *parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_nvridx_x(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
@@ -944,11 +995,12 @@
   p->next_state = and_then;
   p->index = 0xf;
   p->parsing.value = &p->index;
-  return parse_value0(p, cur + 1, end);
+  return parse_value0(exec_ctx, p, cur + 1, end);
 }
 
 /* parse a literal header that is never indexed; index == 0 */
-static grpc_error *parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_lithdr_nvridx_v(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_hpack_parser *p,
                                          const uint8_t *cur,
                                          const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
@@ -956,44 +1008,47 @@
       parse_value_string_with_literal_key, finish_lithdr_nvridx_v};
   p->dynamic_table_update_allowed = 0;
   p->next_state = and_then;
-  return parse_string_prefix(p, cur + 1, end);
+  return parse_string_prefix(exec_ctx, p, cur + 1, end);
 }
 
 /* finish parsing a max table size change */
-static grpc_error *finish_max_tbl_size(grpc_chttp2_hpack_parser *p,
+static grpc_error *finish_max_tbl_size(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end) {
   if (grpc_http_trace) {
     gpr_log(GPR_INFO, "MAX TABLE SIZE: %d", p->index);
   }
   grpc_error *err =
       grpc_chttp2_hptbl_set_current_table_size(&p->table, p->index);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_begin(p, cur, end);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_begin(exec_ctx, p, cur, end);
 }
 
 /* parse a max table size change, max size < 15 */
-static grpc_error *parse_max_tbl_size(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_max_tbl_size(grpc_exec_ctx *exec_ctx,
+                                      grpc_chttp2_hpack_parser *p,
                                       const uint8_t *cur, const uint8_t *end) {
   if (p->dynamic_table_update_allowed == 0) {
     return parse_error(
-        p, cur, end,
+        exec_ctx, p, cur, end,
         GRPC_ERROR_CREATE(
             "More than two max table size changes in a single frame"));
   }
   p->dynamic_table_update_allowed--;
   p->index = (*cur) & 0x1f;
-  return finish_max_tbl_size(p, cur + 1, end);
+  return finish_max_tbl_size(exec_ctx, p, cur + 1, end);
 }
 
 /* parse a max table size change, max size >= 15 */
-static grpc_error *parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_max_tbl_size_x(grpc_exec_ctx *exec_ctx,
+                                        grpc_chttp2_hpack_parser *p,
                                         const uint8_t *cur,
                                         const uint8_t *end) {
   static const grpc_chttp2_hpack_parser_state and_then[] = {
       finish_max_tbl_size};
   if (p->dynamic_table_update_allowed == 0) {
     return parse_error(
-        p, cur, end,
+        exec_ctx, p, cur, end,
         GRPC_ERROR_CREATE(
             "More than two max table size changes in a single frame"));
   }
@@ -1001,11 +1056,12 @@
   p->next_state = and_then;
   p->index = 0x1f;
   p->parsing.value = &p->index;
-  return parse_value0(p, cur + 1, end);
+  return parse_value0(exec_ctx, p, cur + 1, end);
 }
 
 /* a parse error: jam the parse state into parse_error, and return error */
-static grpc_error *parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_error(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                const uint8_t *end, grpc_error *err) {
   GPR_ASSERT(err != GRPC_ERROR_NONE);
   if (p->last_error == GRPC_ERROR_NONE) {
@@ -1015,24 +1071,27 @@
   return err;
 }
 
-static grpc_error *still_parse_error(grpc_chttp2_hpack_parser *p,
+static grpc_error *still_parse_error(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_hpack_parser *p,
                                      const uint8_t *cur, const uint8_t *end) {
   return GRPC_ERROR_REF(p->last_error);
 }
 
-static grpc_error *parse_illegal_op(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_illegal_op(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_hpack_parser *p,
                                     const uint8_t *cur, const uint8_t *end) {
   GPR_ASSERT(cur != end);
   char *msg;
   gpr_asprintf(&msg, "Illegal hpack op code %d", *cur);
   grpc_error *err = GRPC_ERROR_CREATE(msg);
   gpr_free(msg);
-  return parse_error(p, cur, end, err);
+  return parse_error(exec_ctx, p, cur, end, err);
 }
 
 /* parse the 1st byte of a varint into p->parsing.value
    no overflow is possible */
-static grpc_error *parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value0(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value0;
@@ -1042,15 +1101,16 @@
   *p->parsing.value += (*cur) & 0x7f;
 
   if ((*cur) & 0x80) {
-    return parse_value1(p, cur + 1, end);
+    return parse_value1(exec_ctx, p, cur + 1, end);
   } else {
-    return parse_next(p, cur + 1, end);
+    return parse_next(exec_ctx, p, cur + 1, end);
   }
 }
 
 /* parse the 2nd byte of a varint into p->parsing.value
    no overflow is possible */
-static grpc_error *parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value1(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value1;
@@ -1060,15 +1120,16 @@
   *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 7;
 
   if ((*cur) & 0x80) {
-    return parse_value2(p, cur + 1, end);
+    return parse_value2(exec_ctx, p, cur + 1, end);
   } else {
-    return parse_next(p, cur + 1, end);
+    return parse_next(exec_ctx, p, cur + 1, end);
   }
 }
 
 /* parse the 3rd byte of a varint into p->parsing.value
    no overflow is possible */
-static grpc_error *parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value2(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value2;
@@ -1078,15 +1139,16 @@
   *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 14;
 
   if ((*cur) & 0x80) {
-    return parse_value3(p, cur + 1, end);
+    return parse_value3(exec_ctx, p, cur + 1, end);
   } else {
-    return parse_next(p, cur + 1, end);
+    return parse_next(exec_ctx, p, cur + 1, end);
   }
 }
 
 /* parse the 4th byte of a varint into p->parsing.value
    no overflow is possible */
-static grpc_error *parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value3(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end) {
   if (cur == end) {
     p->state = parse_value3;
@@ -1096,15 +1158,16 @@
   *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 21;
 
   if ((*cur) & 0x80) {
-    return parse_value4(p, cur + 1, end);
+    return parse_value4(exec_ctx, p, cur + 1, end);
   } else {
-    return parse_next(p, cur + 1, end);
+    return parse_next(exec_ctx, p, cur + 1, end);
   }
 }
 
 /* parse the 5th byte of a varint into p->parsing.value
    depending on the byte, we may overflow, and care must be taken */
-static grpc_error *parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_value4(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end) {
   uint8_t c;
   uint32_t cur_value;
@@ -1130,9 +1193,9 @@
   *p->parsing.value = cur_value + add_value;
 
   if ((*cur) & 0x80) {
-    return parse_value5up(p, cur + 1, end);
+    return parse_value5up(exec_ctx, p, cur + 1, end);
   } else {
-    return parse_next(p, cur + 1, end);
+    return parse_next(exec_ctx, p, cur + 1, end);
   }
 
 error:
@@ -1142,13 +1205,14 @@
                *p->parsing.value, *cur);
   grpc_error *err = GRPC_ERROR_CREATE(msg);
   gpr_free(msg);
-  return parse_error(p, cur, end, err);
+  return parse_error(exec_ctx, p, cur, end, err);
 }
 
 /* parse any trailing bytes in a varint: it's possible to append an arbitrary
    number of 0x80's and not affect the value - a zero will terminate - and
    anything else will overflow */
-static grpc_error *parse_value5up(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_value5up(grpc_exec_ctx *exec_ctx,
+                                  grpc_chttp2_hpack_parser *p,
                                   const uint8_t *cur, const uint8_t *end) {
   while (cur != end && *cur == 0x80) {
     ++cur;
@@ -1160,7 +1224,7 @@
   }
 
   if (*cur == 0) {
-    return parse_next(p, cur + 1, end);
+    return parse_next(exec_ctx, p, cur + 1, end);
   }
 
   char *msg;
@@ -1170,11 +1234,12 @@
                *p->parsing.value, *cur);
   grpc_error *err = GRPC_ERROR_CREATE(msg);
   gpr_free(msg);
-  return parse_error(p, cur, end, err);
+  return parse_error(exec_ctx, p, cur, end, err);
 }
 
 /* parse a string prefix */
-static grpc_error *parse_string_prefix(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_string_prefix(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_hpack_parser *p,
                                        const uint8_t *cur, const uint8_t *end) {
   if (cur == end) {
     p->state = parse_string_prefix;
@@ -1185,9 +1250,9 @@
   p->huff = (*cur) >> 7;
   if (p->strlen == 0x7f) {
     p->parsing.value = &p->strlen;
-    return parse_value0(p, cur + 1, end);
+    return parse_value0(exec_ctx, p, cur + 1, end);
   } else {
-    return parse_next(p, cur + 1, end);
+    return parse_next(exec_ctx, p, cur + 1, end);
   }
 }
 
@@ -1205,7 +1270,8 @@
   str->length += (uint32_t)length;
 }
 
-static grpc_error *append_string(grpc_chttp2_hpack_parser *p,
+static grpc_error *append_string(grpc_exec_ctx *exec_ctx,
+                                 grpc_chttp2_hpack_parser *p,
                                  const uint8_t *cur, const uint8_t *end) {
   grpc_chttp2_hpack_parser_string *str = p->parsing.str;
   uint32_t bits;
@@ -1223,7 +1289,7 @@
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return parse_error(p, cur, end,
+        return parse_error(exec_ctx, p, cur, end,
                            GRPC_ERROR_CREATE("Illegal base64 character"));
       else if (bits == 64)
         goto b64_byte0;
@@ -1238,7 +1304,7 @@
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return parse_error(p, cur, end,
+        return parse_error(exec_ctx, p, cur, end,
                            GRPC_ERROR_CREATE("Illegal base64 character"));
       else if (bits == 64)
         goto b64_byte1;
@@ -1253,7 +1319,7 @@
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return parse_error(p, cur, end,
+        return parse_error(exec_ctx, p, cur, end,
                            GRPC_ERROR_CREATE("Illegal base64 character"));
       else if (bits == 64)
         goto b64_byte2;
@@ -1268,7 +1334,7 @@
       bits = inverse_base64[*cur];
       ++cur;
       if (bits == 255)
-        return parse_error(p, cur, end,
+        return parse_error(exec_ctx, p, cur, end,
                            GRPC_ERROR_CREATE("Illegal base64 character"));
       else if (bits == 64)
         goto b64_byte3;
@@ -1281,11 +1347,12 @@
       goto b64_byte0;
   }
   GPR_UNREACHABLE_CODE(return parse_error(
-      p, cur, end, GRPC_ERROR_CREATE("Should never reach here")));
+      exec_ctx, p, cur, end, GRPC_ERROR_CREATE("Should never reach here")));
 }
 
 /* append a null terminator to a string */
-static grpc_error *finish_str(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *finish_str(grpc_exec_ctx *exec_ctx,
+                              grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                               const uint8_t *end) {
   uint8_t terminator = 0;
   uint8_t decoded[2];
@@ -1298,7 +1365,7 @@
       break;
     case B64_BYTE1:
       return parse_error(
-          p, cur, end,
+          exec_ctx, p, cur, end,
           GRPC_ERROR_CREATE("illegal base64 encoding")); /* illegal encoding */
     case B64_BYTE2:
       bits = p->base64_buffer;
@@ -1308,7 +1375,7 @@
                      bits & 0xffff);
         grpc_error *err = GRPC_ERROR_CREATE(msg);
         gpr_free(msg);
-        return parse_error(p, cur, end, err);
+        return parse_error(exec_ctx, p, cur, end, err);
       }
       decoded[0] = (uint8_t)(bits >> 16);
       append_bytes(str, decoded, 1);
@@ -1321,7 +1388,7 @@
                      bits & 0xff);
         grpc_error *err = GRPC_ERROR_CREATE(msg);
         gpr_free(msg);
-        return parse_error(p, cur, end, err);
+        return parse_error(exec_ctx, p, cur, end, err);
       }
       decoded[0] = (uint8_t)(bits >> 16);
       decoded[1] = (uint8_t)(bits >> 8);
@@ -1334,13 +1401,14 @@
 }
 
 /* decode a nibble from a huffman encoded stream */
-static grpc_error *huff_nibble(grpc_chttp2_hpack_parser *p, uint8_t nibble) {
+static grpc_error *huff_nibble(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_hpack_parser *p, uint8_t nibble) {
   int16_t emit = emit_sub_tbl[16 * emit_tbl[p->huff_state] + nibble];
   int16_t next = next_sub_tbl[16 * next_tbl[p->huff_state] + nibble];
   if (emit != -1) {
     if (emit >= 0 && emit < 256) {
       uint8_t c = (uint8_t)emit;
-      grpc_error *err = append_string(p, &c, (&c) + 1);
+      grpc_error *err = append_string(exec_ctx, p, &c, (&c) + 1);
       if (err != GRPC_ERROR_NONE) return err;
     } else {
       assert(emit == 256);
@@ -1351,42 +1419,45 @@
 }
 
 /* decode full bytes from a huffman encoded stream */
-static grpc_error *add_huff_bytes(grpc_chttp2_hpack_parser *p,
+static grpc_error *add_huff_bytes(grpc_exec_ctx *exec_ctx,
+                                  grpc_chttp2_hpack_parser *p,
                                   const uint8_t *cur, const uint8_t *end) {
   for (; cur != end; ++cur) {
-    grpc_error *err = huff_nibble(p, *cur >> 4);
-    if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-    err = huff_nibble(p, *cur & 0xf);
-    if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
+    grpc_error *err = huff_nibble(exec_ctx, p, *cur >> 4);
+    if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+    err = huff_nibble(exec_ctx, p, *cur & 0xf);
+    if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
   }
   return GRPC_ERROR_NONE;
 }
 
 /* decode some string bytes based on the current decoding mode
    (huffman or not) */
-static grpc_error *add_str_bytes(grpc_chttp2_hpack_parser *p,
+static grpc_error *add_str_bytes(grpc_exec_ctx *exec_ctx,
+                                 grpc_chttp2_hpack_parser *p,
                                  const uint8_t *cur, const uint8_t *end) {
   if (p->huff) {
-    return add_huff_bytes(p, cur, end);
+    return add_huff_bytes(exec_ctx, p, cur, end);
   } else {
-    return append_string(p, cur, end);
+    return append_string(exec_ctx, p, cur, end);
   }
 }
 
 /* parse a string - tries to do large chunks at a time */
-static grpc_error *parse_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+static grpc_error *parse_string(grpc_exec_ctx *exec_ctx,
+                                grpc_chttp2_hpack_parser *p, const uint8_t *cur,
                                 const uint8_t *end) {
   size_t remaining = p->strlen - p->strgot;
   size_t given = (size_t)(end - cur);
   if (remaining <= given) {
-    grpc_error *err = add_str_bytes(p, cur, cur + remaining);
-    if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-    err = finish_str(p, cur + remaining, end);
-    if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-    return parse_next(p, cur + remaining, end);
+    grpc_error *err = add_str_bytes(exec_ctx, p, cur, cur + remaining);
+    if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+    err = finish_str(exec_ctx, p, cur + remaining, end);
+    if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+    return parse_next(exec_ctx, p, cur + remaining, end);
   } else {
-    grpc_error *err = add_str_bytes(p, cur, cur + given);
-    if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
+    grpc_error *err = add_str_bytes(exec_ctx, p, cur, cur + given);
+    if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
     GPR_ASSERT(given <= UINT32_MAX - p->strgot);
     p->strgot += (uint32_t)given;
     p->state = parse_string;
@@ -1395,7 +1466,8 @@
 }
 
 /* begin parsing a string - performs setup, calls parse_string */
-static grpc_error *begin_parse_string(grpc_chttp2_hpack_parser *p,
+static grpc_error *begin_parse_string(grpc_exec_ctx *exec_ctx,
+                                      grpc_chttp2_hpack_parser *p,
                                       const uint8_t *cur, const uint8_t *end,
                                       uint8_t binary,
                                       grpc_chttp2_hpack_parser_string *str) {
@@ -1404,13 +1476,14 @@
   p->parsing.str = str;
   p->huff_state = 0;
   p->binary = binary;
-  return parse_string(p, cur, end);
+  return parse_string(exec_ctx, p, cur, end);
 }
 
 /* parse the key string */
-static grpc_error *parse_key_string(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_key_string(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_hpack_parser *p,
                                     const uint8_t *cur, const uint8_t *end) {
-  return begin_parse_string(p, cur, end, NOT_BINARY, &p->key);
+  return begin_parse_string(exec_ctx, p, cur, end, NOT_BINARY, &p->key);
 }
 
 /* check if a key represents a binary header or not */
@@ -1435,24 +1508,27 @@
 }
 
 /* parse the value string */
-static grpc_error *parse_value_string(grpc_chttp2_hpack_parser *p,
+static grpc_error *parse_value_string(grpc_exec_ctx *exec_ctx,
+                                      grpc_chttp2_hpack_parser *p,
                                       const uint8_t *cur, const uint8_t *end,
                                       bool is_binary) {
-  return begin_parse_string(p, cur, end, is_binary ? B64_BYTE0 : NOT_BINARY,
-                            &p->value);
+  return begin_parse_string(exec_ctx, p, cur, end,
+                            is_binary ? B64_BYTE0 : NOT_BINARY, &p->value);
 }
 
 static grpc_error *parse_value_string_with_indexed_key(
-    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end) {
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+    const uint8_t *end) {
   bool is_binary = false;
   grpc_error *err = is_binary_indexed_header(p, &is_binary);
-  if (err != GRPC_ERROR_NONE) return parse_error(p, cur, end, err);
-  return parse_value_string(p, cur, end, is_binary);
+  if (err != GRPC_ERROR_NONE) return parse_error(exec_ctx, p, cur, end, err);
+  return parse_value_string(exec_ctx, p, cur, end, is_binary);
 }
 
 static grpc_error *parse_value_string_with_literal_key(
-    grpc_chttp2_hpack_parser *p, const uint8_t *cur, const uint8_t *end) {
-  return parse_value_string(p, cur, end, is_binary_literal_header(p));
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_parser *p, const uint8_t *cur,
+    const uint8_t *end) {
+  return parse_value_string(exec_ctx, p, cur, end, is_binary_literal_header(p));
 }
 
 /* PUBLIC INTERFACE */
@@ -1484,27 +1560,36 @@
   gpr_free(p->value.str);
 }
 
-grpc_error *grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
+grpc_error *grpc_chttp2_hpack_parser_parse(grpc_exec_ctx *exec_ctx,
+                                           grpc_chttp2_hpack_parser *p,
                                            const uint8_t *beg,
                                            const uint8_t *end) {
   /* TODO(ctiller): limit the distance of end from beg, and perform multiple
      steps in the event of a large chunk of data to limit
      stack space usage when no tail call optimization is
      available */
-  return p->state(p, beg, end);
+  return p->state(exec_ctx, p, beg, end);
 }
 
-grpc_error *grpc_chttp2_header_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *hpack_parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) {
+typedef void (*maybe_complete_func_type)(grpc_exec_ctx *exec_ctx,
+                                         grpc_chttp2_transport *t,
+                                         grpc_chttp2_stream *s);
+static const maybe_complete_func_type maybe_complete_funcs[] = {
+    grpc_chttp2_maybe_complete_recv_initial_metadata,
+    grpc_chttp2_maybe_complete_recv_trailing_metadata};
+
+grpc_error *grpc_chttp2_header_parser_parse(grpc_exec_ctx *exec_ctx,
+                                            void *hpack_parser,
+                                            grpc_chttp2_transport *t,
+                                            grpc_chttp2_stream *s,
+                                            gpr_slice slice, int is_last) {
   grpc_chttp2_hpack_parser *parser = hpack_parser;
   GPR_TIMER_BEGIN("grpc_chttp2_hpack_parser_parse", 0);
-  if (stream_parsing != NULL) {
-    stream_parsing->stats.incoming.header_bytes += GPR_SLICE_LENGTH(slice);
+  if (s != NULL) {
+    s->stats.incoming.header_bytes += GPR_SLICE_LENGTH(slice);
   }
   grpc_error *error = grpc_chttp2_hpack_parser_parse(
-      parser, GPR_SLICE_START_PTR(slice), GPR_SLICE_END_PTR(slice));
+      exec_ctx, parser, GPR_SLICE_START_PTR(slice), GPR_SLICE_END_PTR(slice));
   if (error != GRPC_ERROR_NONE) {
     GPR_TIMER_END("grpc_chttp2_hpack_parser_parse", 0);
     return error;
@@ -1517,20 +1602,19 @@
     }
     /* need to check for null stream: this can occur if we receive an invalid
        stream id on a header */
-    if (stream_parsing != NULL) {
+    if (s != NULL) {
       if (parser->is_boundary) {
-        if (stream_parsing->header_frames_received ==
-            GPR_ARRAY_SIZE(stream_parsing->got_metadata_on_parse)) {
+        if (s->header_frames_received == GPR_ARRAY_SIZE(s->metadata_buffer)) {
           return GRPC_ERROR_CREATE("Too many trailer frames");
         }
-        stream_parsing
-            ->got_metadata_on_parse[stream_parsing->header_frames_received] = 1;
-        stream_parsing->header_frames_received++;
-        grpc_chttp2_list_add_parsing_seen_stream(transport_parsing,
-                                                 stream_parsing);
+        s->published_metadata[s->header_frames_received] =
+            GRPC_METADATA_PUBLISHED_FROM_WIRE;
+        maybe_complete_funcs[s->header_frames_received](exec_ctx, t, s);
+        s->header_frames_received++;
       }
       if (parser->is_eof) {
-        stream_parsing->received_close = 1;
+        grpc_chttp2_mark_stream_closed(exec_ctx, t, s, true, false,
+                                       GRPC_ERROR_NONE);
       }
     }
     parser->on_header = NULL;
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.h b/src/core/ext/transport/chttp2/transport/hpack_parser.h
index 78eb38d..0290c78 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.h
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.h
@@ -45,7 +45,8 @@
 typedef struct grpc_chttp2_hpack_parser grpc_chttp2_hpack_parser;
 
 typedef grpc_error *(*grpc_chttp2_hpack_parser_state)(
-    grpc_chttp2_hpack_parser *p, const uint8_t *beg, const uint8_t *end);
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_parser *p, const uint8_t *beg,
+    const uint8_t *end);
 
 typedef struct {
   char *str;
@@ -55,7 +56,7 @@
 
 struct grpc_chttp2_hpack_parser {
   /* user specified callback for each header output */
-  void (*on_header)(void *user_data, grpc_mdelem *md);
+  void (*on_header)(grpc_exec_ctx *exec_ctx, void *user_data, grpc_mdelem *md);
   void *on_header_user_data;
 
   grpc_error *last_error;
@@ -104,15 +105,17 @@
 void grpc_chttp2_hpack_parser_set_has_priority(grpc_chttp2_hpack_parser *p);
 
 /* returns 1 on success, 0 on error */
-grpc_error *grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p,
+grpc_error *grpc_chttp2_hpack_parser_parse(grpc_exec_ctx *exec_ctx,
+                                           grpc_chttp2_hpack_parser *p,
                                            const uint8_t *beg,
                                            const uint8_t *end);
 
 /* wraps grpc_chttp2_hpack_parser_parse to provide a frame level parser for
    the transport */
-grpc_error *grpc_chttp2_header_parser_parse(
-    grpc_exec_ctx *exec_ctx, void *hpack_parser,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last);
+grpc_error *grpc_chttp2_header_parser_parse(grpc_exec_ctx *exec_ctx,
+                                            void *hpack_parser,
+                                            grpc_chttp2_transport *t,
+                                            grpc_chttp2_stream *s,
+                                            gpr_slice slice, int is_last);
 
 #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_PARSER_H */
diff --git a/src/core/ext/transport/chttp2/transport/internal.h b/src/core/ext/transport/chttp2/transport/internal.h
index 04b788b..01f521c 100644
--- a/src/core/ext/transport/chttp2/transport/internal.h
+++ b/src/core/ext/transport/chttp2/transport/internal.h
@@ -53,31 +53,25 @@
 #include "src/core/lib/transport/connectivity_state.h"
 #include "src/core/lib/transport/transport_impl.h"
 
-typedef struct grpc_chttp2_transport grpc_chttp2_transport;
-typedef struct grpc_chttp2_stream grpc_chttp2_stream;
-
 /* streams are kept in various linked lists depending on what things need to
    happen to them... this enum labels each list */
 typedef enum {
-  GRPC_CHTTP2_LIST_ALL_STREAMS,
-  GRPC_CHTTP2_LIST_CHECK_READ_OPS,
-  GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE,
   GRPC_CHTTP2_LIST_WRITABLE,
   GRPC_CHTTP2_LIST_WRITING,
-  GRPC_CHTTP2_LIST_WRITTEN,
-  GRPC_CHTTP2_LIST_PARSING_SEEN,
-  GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_PARSING,
-  GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_WRITING,
   GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT,
-  /* streams waiting for the outgoing window in the writing path, they will be
-   * merged to the stalled list or writable list under transport lock. */
-  GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT,
   /** streams that are waiting to start because there are too many concurrent
       streams on the connection */
   GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY,
   STREAM_LIST_COUNT /* must be last */
 } grpc_chttp2_stream_list_id;
 
+typedef enum {
+  GRPC_CHTTP2_WRITE_STATE_IDLE,
+  GRPC_CHTTP2_WRITE_STATE_WRITING,
+  GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE,
+  GRPC_CHTTP2_WRITE_STATE_WRITING_WITH_MORE_AND_COVERED_BY_POLLER,
+} grpc_chttp2_write_state;
+
 /* deframer state for the overall http2 stream of bytes */
 typedef enum {
   /* prefix: one entry per http2 connection prefix byte */
@@ -152,6 +146,12 @@
   struct grpc_chttp2_outstanding_ping *prev;
 } grpc_chttp2_outstanding_ping;
 
+typedef struct grpc_chttp2_write_cb {
+  int64_t call_at_byte;
+  grpc_closure *closure;
+  struct grpc_chttp2_write_cb *next;
+} grpc_chttp2_write_cb;
+
 /* forward declared in frame_data.h */
 struct grpc_chttp2_incoming_byte_stream {
   grpc_byte_stream base;
@@ -161,12 +161,13 @@
 
   grpc_chttp2_transport *transport;
   grpc_chttp2_stream *stream;
-  int is_tail;
+  bool is_tail;
 
   gpr_mu slice_mu;  // protects slices, on_next
   gpr_slice_buffer slices;
   grpc_closure *on_next;
   gpr_slice *next;
+  uint32_t remaining_bytes;
 
   struct {
     grpc_closure closure;
@@ -178,172 +179,16 @@
   grpc_closure finished_action;
 };
 
-typedef struct {
-  /** data to write next write */
-  gpr_slice_buffer qbuf;
-
-  /** window available for us to send to peer */
-  int64_t outgoing_window;
-  /** window available to announce to peer */
-  int64_t announce_incoming_window;
-  /** how much window would we like to have for incoming_window */
-  uint32_t connection_window_target;
-
-  /** have we seen a goaway */
-  uint8_t seen_goaway;
-  /** have we sent a goaway */
-  uint8_t sent_goaway;
-
-  /** is this transport a client? */
-  uint8_t is_client;
-  /** are the local settings dirty and need to be sent? */
-  uint8_t dirtied_local_settings;
-  /** have local settings been sent? */
-  uint8_t sent_local_settings;
-  /** bitmask of setting indexes to send out */
-  uint32_t force_send_settings;
-  /** settings values */
-  uint32_t settings[GRPC_NUM_SETTING_SETS][GRPC_CHTTP2_NUM_SETTINGS];
-
-  /** what is the next stream id to be allocated by this peer?
-      copied to next_stream_id in parsing when parsing commences */
-  uint32_t next_stream_id;
-
-  /** how far to lookahead in a stream? */
-  uint32_t stream_lookahead;
-
-  /** last received stream id */
-  uint32_t last_incoming_stream_id;
-
-  /** pings awaiting responses */
-  grpc_chttp2_outstanding_ping pings;
-  /** next payload for an outgoing ping */
-  uint64_t ping_counter;
-
-  /** concurrent stream count: updated when not parsing,
-      so this is a strict over-estimation on the client */
-  uint32_t concurrent_stream_count;
-} grpc_chttp2_transport_global;
-
-typedef struct {
-  /** data to write now */
-  gpr_slice_buffer outbuf;
-  /** hpack encoding */
-  grpc_chttp2_hpack_compressor hpack_compressor;
-  int64_t outgoing_window;
-  /** is this a client? */
-  uint8_t is_client;
-  /** callback for when writing is done */
-  grpc_closure done_cb;
-  /** maximum frame size */
-  uint32_t max_frame_size;
-} grpc_chttp2_transport_writing;
-
-struct grpc_chttp2_transport_parsing {
-  /** is this transport a client? (boolean) */
-  uint8_t is_client;
-
-  /** were settings updated? */
-  uint8_t settings_updated;
-  /** was a settings ack received? */
-  uint8_t settings_ack_received;
-  /** was a goaway frame received? */
-  uint8_t goaway_received;
-
-  /** initial window change */
-  int64_t initial_window_update;
-
-  /** data to write later - after parsing */
-  gpr_slice_buffer qbuf;
-  /** parser for headers */
-  grpc_chttp2_hpack_parser hpack_parser;
-  /** simple one shot parsers */
-  union {
-    grpc_chttp2_window_update_parser window_update;
-    grpc_chttp2_settings_parser settings;
-    grpc_chttp2_ping_parser ping;
-    grpc_chttp2_rst_stream_parser rst_stream;
-  } simple;
-  /** parser for goaway frames */
-  grpc_chttp2_goaway_parser goaway_parser;
-
-  /** window available for peer to send to us */
-  int64_t incoming_window;
-
-  /** next stream id available at the time of beginning parsing */
-  uint32_t next_stream_id;
-  uint32_t last_incoming_stream_id;
-
-  /* deframing */
-  grpc_chttp2_deframe_transport_state deframe_state;
-  uint8_t incoming_frame_type;
-  uint8_t incoming_frame_flags;
-  uint8_t header_eof;
-  bool is_first_frame;
-  uint32_t expect_continuation_stream_id;
-  uint32_t incoming_frame_size;
-  uint32_t incoming_stream_id;
-
-  /* current max frame size */
-  uint32_t max_frame_size;
-
-  /* active parser */
-  void *parser_data;
-  grpc_chttp2_stream_parsing *incoming_stream;
-  grpc_error *(*parser)(grpc_exec_ctx *exec_ctx, void *parser_user_data,
-                        grpc_chttp2_transport_parsing *transport_parsing,
-                        grpc_chttp2_stream_parsing *stream_parsing,
-                        gpr_slice slice, int is_last);
-
-  /* received settings */
-  uint32_t settings[GRPC_CHTTP2_NUM_SETTINGS];
-  /* last settings that were sent */
-  uint32_t last_sent_settings[GRPC_CHTTP2_NUM_SETTINGS];
-
-  /* goaway data */
-  grpc_status_code goaway_error;
-  uint32_t goaway_last_stream_index;
-  gpr_slice goaway_text;
-
-  int64_t outgoing_window;
-};
-
-typedef enum {
-  /** no writing activity allowed */
-  GRPC_CHTTP2_WRITES_CORKED,
-  /** no writing activity */
-  GRPC_CHTTP2_WRITING_INACTIVE,
-  /** write has been requested and scheduled against the workqueue */
-  GRPC_CHTTP2_WRITE_SCHEDULED,
-  /** write has been initiated after being reaped from the workqueue */
-  GRPC_CHTTP2_WRITING,
-  /** write has been initiated, AND another write needs to be started once it's
-      done */
-  GRPC_CHTTP2_WRITING_STALE_WITH_POLLER,
-  GRPC_CHTTP2_WRITING_STALE_NO_POLLER,
-} grpc_chttp2_write_state;
-
 struct grpc_chttp2_transport {
   grpc_transport base; /* must be first */
   gpr_refcount refs;
   grpc_endpoint *ep;
   char *peer_string;
 
-  /** when this drops to zero it's safe to shutdown the endpoint */
-  gpr_refcount shutdown_ep_refs;
+  grpc_combiner *combiner;
 
-  struct {
-    grpc_combiner *combiner;
-
-    /** is a thread currently in the global lock */
-    bool global_active;
-    /** is a thread currently parsing */
-    bool parsing_active;
-    /** write execution state of the transport */
-    grpc_chttp2_write_state write_state;
-    /** has a check_read_ops been scheduled */
-    bool check_read_ops_scheduled;
-  } executor;
+  /** write execution state of the transport */
+  grpc_chttp2_write_state write_state;
 
   /** is the transport destroying itself? */
   uint8_t destroying;
@@ -356,39 +201,16 @@
   /** various lists of streams */
   grpc_chttp2_stream_list lists[STREAM_LIST_COUNT];
 
-  /** global state for reading/writing */
-  grpc_chttp2_transport_global global;
-  /** state only accessible by the chain of execution that
-      set writing_state >= GRPC_WRITING, and only by the writing closure
-      chain. */
-  grpc_chttp2_transport_writing writing;
-  /** state only accessible by the chain of execution that
-      set parsing_active=1 */
-  grpc_chttp2_transport_parsing parsing;
+  /** maps stream id to grpc_chttp2_stream objects */
+  grpc_chttp2_stream_map stream_map;
 
-  /** maps stream id to grpc_chttp2_stream objects;
-      owned by the parsing thread when parsing */
-  grpc_chttp2_stream_map parsing_stream_map;
+  grpc_closure write_action_begin_locked;
+  grpc_closure write_action;
+  grpc_closure write_action_end;
+  grpc_closure write_action_end_locked;
 
-  /** streams created by the client (possibly during parsing);
-      merged with parsing_stream_map during unlock when no
-      parsing is occurring */
-  grpc_chttp2_stream_map new_stream_map;
-
-  /** closure to execute writing */
-  grpc_closure writing_action;
-  /** closure to start reading from the endpoint */
-  grpc_closure reading_action;
-  grpc_closure reading_action_locked;
-  grpc_closure post_parse_locked;
-  /** closure to actually do parsing */
-  grpc_closure parsing_action;
-  /** closure to initiate writing */
-  grpc_closure initiate_writing;
-  /** closure to finish writing */
-  grpc_closure terminate_writing;
-  /** closure to flush read state up the stack */
-  grpc_closure initiate_read_flush_locked;
+  grpc_closure read_action_begin;
+  grpc_closure read_action_locked;
 
   /** incoming read bytes */
   gpr_slice_buffer read_buffer;
@@ -408,11 +230,115 @@
     grpc_connectivity_state_tracker state_tracker;
   } channel_callback;
 
-  /** Transport op to be applied post-parsing */
-  grpc_transport_op *post_parsing_op;
+  /** data to write now */
+  gpr_slice_buffer outbuf;
+  /** hpack encoding */
+  grpc_chttp2_hpack_compressor hpack_compressor;
+  int64_t outgoing_window;
+  /** is this a client? */
+  uint8_t is_client;
+
+  /** data to write next write */
+  gpr_slice_buffer qbuf;
+
+  /** window available to announce to peer */
+  int64_t announce_incoming_window;
+  /** how much window would we like to have for incoming_window */
+  uint32_t connection_window_target;
+
+  /** have we seen a goaway */
+  uint8_t seen_goaway;
+  /** have we sent a goaway */
+  uint8_t sent_goaway;
+
+  /** are the local settings dirty and need to be sent? */
+  uint8_t dirtied_local_settings;
+  /** have local settings been sent? */
+  uint8_t sent_local_settings;
+  /** bitmask of setting indexes to send out */
+  uint32_t force_send_settings;
+  /** settings values */
+  uint32_t settings[GRPC_NUM_SETTING_SETS][GRPC_CHTTP2_NUM_SETTINGS];
+
+  /** what is the next stream id to be allocated by this peer?
+      copied to next_stream_id in parsing when parsing commences */
+  uint32_t next_stream_id;
+
+  /** how far to lookahead in a stream? */
+  uint32_t stream_lookahead;
+
+  /** last new stream id */
+  uint32_t last_new_stream_id;
+
+  /** pings awaiting responses */
+  grpc_chttp2_outstanding_ping pings;
+  /** next payload for an outgoing ping */
+  uint64_t ping_counter;
+
+  /** parser for headers */
+  grpc_chttp2_hpack_parser hpack_parser;
+  /** simple one shot parsers */
+  union {
+    grpc_chttp2_window_update_parser window_update;
+    grpc_chttp2_settings_parser settings;
+    grpc_chttp2_ping_parser ping;
+    grpc_chttp2_rst_stream_parser rst_stream;
+  } simple;
+  /** parser for goaway frames */
+  grpc_chttp2_goaway_parser goaway_parser;
+
+  /** initial window change */
+  int64_t initial_window_update;
+
+  /** window available for peer to send to us */
+  int64_t incoming_window;
+
+  /* deframing */
+  grpc_chttp2_deframe_transport_state deframe_state;
+  uint8_t incoming_frame_type;
+  uint8_t incoming_frame_flags;
+  uint8_t header_eof;
+  bool is_first_frame;
+  uint32_t expect_continuation_stream_id;
+  uint32_t incoming_frame_size;
+  uint32_t incoming_stream_id;
+
+  /* active parser */
+  void *parser_data;
+  grpc_chttp2_stream *incoming_stream;
+  grpc_error *(*parser)(grpc_exec_ctx *exec_ctx, void *parser_user_data,
+                        grpc_chttp2_transport *t, grpc_chttp2_stream *s,
+                        gpr_slice slice, int is_last);
+
+  /* goaway data */
+  grpc_status_code goaway_error;
+  uint32_t goaway_last_stream_index;
+  gpr_slice goaway_text;
+
+  grpc_chttp2_write_cb *write_cb_pool;
+
+  /* if non-NULL, close the transport with this error when writes are finished
+   */
+  grpc_error *close_transport_on_writes_finished;
 };
 
-typedef struct {
+typedef enum {
+  GRPC_METADATA_NOT_PUBLISHED,
+  GRPC_METADATA_SYNTHESIZED_FROM_FAKE,
+  GRPC_METADATA_PUBLISHED_FROM_WIRE,
+  GPRC_METADATA_PUBLISHED_AT_CLOSE
+} grpc_published_metadata_method;
+
+struct grpc_chttp2_stream {
+  grpc_chttp2_transport *t;
+  grpc_stream_refcount *refcount;
+
+  grpc_closure destroy_stream;
+  void *destroy_stream_arg;
+
+  grpc_chttp2_stream_link links[STREAM_LIST_COUNT];
+  uint8_t included[STREAM_LIST_COUNT];
+
   /** HTTP2 stream id for this stream, or zero if one has not been assigned */
   uint32_t id;
 
@@ -422,20 +348,22 @@
       As the upper layer offers more bytes, this value increases.
       As bytes are read, this value decreases. */
   uint32_t max_recv_bytes;
-  /** The number of bytes the upper layer has offered to read but we have
-      not yet announced to HTTP2 flow control.
-      As the upper layers offer to read more bytes, this value increases.
-      As we advertise incoming flow control window, this value decreases. */
-  uint32_t unannounced_incoming_window_for_parse;
-  uint32_t unannounced_incoming_window_for_writing;
   /** things the upper layers would like to send */
   grpc_metadata_batch *send_initial_metadata;
   grpc_closure *send_initial_metadata_finished;
-  grpc_byte_stream *send_message;
-  grpc_closure *send_message_finished;
   grpc_metadata_batch *send_trailing_metadata;
   grpc_closure *send_trailing_metadata_finished;
 
+  grpc_byte_stream *fetching_send_message;
+  uint32_t fetched_send_message_length;
+  gpr_slice fetching_slice;
+  int64_t next_message_end_offset;
+  int64_t flow_controlled_bytes_written;
+  bool complete_fetch_covered_by_poller;
+  grpc_closure complete_fetch;
+  grpc_closure complete_fetch_locked;
+  grpc_closure *fetching_send_message_finished;
+
   grpc_metadata_batch *recv_initial_metadata;
   grpc_closure *recv_initial_metadata_ready;
   grpc_byte_stream **recv_message;
@@ -455,95 +383,44 @@
   bool read_closed;
   /** Are all published incoming byte streams closed. */
   bool all_incoming_byte_streams_finished;
-  /** Is this stream in the stream map. */
-  bool in_stream_map;
   /** Has this stream seen an error.
       If true, then pending incoming frames can be thrown away. */
   bool seen_error;
-  bool exceeded_metadata_size;
 
   /** the error that resulted in this stream being read-closed */
   grpc_error *read_closed_error;
   /** the error that resulted in this stream being write-closed */
   grpc_error *write_closed_error;
 
-  bool published_initial_metadata;
-  bool published_trailing_metadata;
+  grpc_published_metadata_method published_metadata[2];
   bool final_metadata_requested;
 
-  grpc_chttp2_incoming_metadata_buffer received_initial_metadata;
-  grpc_chttp2_incoming_metadata_buffer received_trailing_metadata;
+  grpc_chttp2_incoming_metadata_buffer metadata_buffer[2];
 
   grpc_chttp2_incoming_frame_queue incoming_frames;
 
   gpr_timespec deadline;
-} grpc_chttp2_stream_global;
 
-typedef struct {
-  /** HTTP2 stream id for this stream, or zero if one has not been assigned */
-  uint32_t id;
-  uint8_t fetching;
-  bool sent_initial_metadata;
-  uint8_t sent_message;
-  uint8_t sent_trailing_metadata;
-  uint8_t read_closed;
-  /** send this initial metadata */
-  grpc_metadata_batch *send_initial_metadata;
-  grpc_byte_stream *send_message;
-  grpc_metadata_batch *send_trailing_metadata;
-  int64_t outgoing_window;
-  /** how much window should we announce? */
-  uint32_t announce_window;
-  gpr_slice_buffer flow_controlled_buffer;
-  gpr_slice fetching_slice;
-  size_t stream_fetched;
-  grpc_closure finished_fetch;
-  /** stats gathered during the write */
-  grpc_transport_one_way_stats stats;
-} grpc_chttp2_stream_writing;
-
-struct grpc_chttp2_stream_parsing {
   /** saw some stream level error */
   grpc_error *forced_close_error;
-  /** HTTP2 stream id for this stream, or zero if one has not been assigned */
-  uint32_t id;
-  /** has this stream received a close */
-  uint8_t received_close;
   /** how many header frames have we received? */
   uint8_t header_frames_received;
-  /** which metadata did we get (on this parse) */
-  uint8_t got_metadata_on_parse[2];
-  /** should we raise the seen_error flag in transport_global */
-  bool seen_error;
-  bool exceeded_metadata_size;
   /** window available for peer to send to us */
   int64_t incoming_window;
   /** parsing state for data frames */
   grpc_chttp2_data_parser data_parser;
-  /** amount of window given */
-  int64_t outgoing_window;
   /** number of bytes received - reset at end of parse thread execution */
   int64_t received_bytes;
-  /** stats gathered during the parse */
-  grpc_transport_stream_stats stats;
 
-  /** incoming metadata */
-  grpc_chttp2_incoming_metadata_buffer metadata_buffer[2];
-};
+  bool sent_initial_metadata;
+  bool sent_trailing_metadata;
+  /** how much window should we announce? */
+  uint32_t announce_window;
+  gpr_slice_buffer flow_controlled_buffer;
 
-struct grpc_chttp2_stream {
-  grpc_chttp2_transport *t;
-  grpc_stream_refcount *refcount;
-  grpc_chttp2_stream_global global;
-  grpc_chttp2_stream_writing writing;
-  grpc_chttp2_stream_parsing parsing;
-
-  grpc_closure init_stream;
-  grpc_closure destroy_stream;
-  void *destroy_stream_arg;
-
-  grpc_chttp2_stream_link links[STREAM_LIST_COUNT];
-  uint8_t included[STREAM_LIST_COUNT];
+  grpc_chttp2_write_cb *on_write_finished_cbs;
+  grpc_chttp2_write_cb *finish_after_write;
+  size_t sending_bytes;
 };
 
 /** Transport writing call flow:
@@ -559,162 +436,71 @@
     The actual call chain is documented in the implementation of this function.
     */
 void grpc_chttp2_initiate_write(grpc_exec_ctx *exec_ctx,
-                                grpc_chttp2_transport_global *transport_global,
+                                grpc_chttp2_transport *t,
                                 bool covered_by_poller, const char *reason);
 
 /** Someone is unlocking the transport mutex: check to see if writes
-    are required, and schedule them if so */
-int grpc_chttp2_unlocking_check_writes(grpc_exec_ctx *exec_ctx,
-                                       grpc_chttp2_transport_global *global,
-                                       grpc_chttp2_transport_writing *writing);
-void grpc_chttp2_perform_writes(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing,
-    grpc_endpoint *endpoint);
-void grpc_chttp2_terminate_writing(grpc_exec_ctx *exec_ctx,
-                                   void *transport_writing, grpc_error *error);
-void grpc_chttp2_cleanup_writing(grpc_exec_ctx *exec_ctx,
-                                 grpc_chttp2_transport_global *global,
-                                 grpc_chttp2_transport_writing *writing);
+    are required, and frame them if so */
+bool grpc_chttp2_begin_write(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t);
+void grpc_chttp2_end_write(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                           grpc_error *error);
 
-void grpc_chttp2_prepare_to_read(grpc_chttp2_transport_global *global,
-                                 grpc_chttp2_transport_parsing *parsing);
 /** Process one slice of incoming data; return 1 if the connection is still
     viable after reading, or 0 if the connection should be torn down */
-grpc_error *grpc_chttp2_perform_read(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    gpr_slice slice);
-void grpc_chttp2_publish_reads(grpc_exec_ctx *exec_ctx,
-                               grpc_chttp2_transport_global *global,
-                               grpc_chttp2_transport_parsing *parsing);
+grpc_error *grpc_chttp2_perform_read(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t, gpr_slice slice);
 
-bool grpc_chttp2_list_add_writable_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
+bool grpc_chttp2_list_add_writable_stream(grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s);
 /** Get a writable stream
     returns non-zero if there was a stream available */
-int grpc_chttp2_list_pop_writable_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_writing **stream_writing);
+int grpc_chttp2_list_pop_writable_stream(grpc_chttp2_transport *t,
+                                         grpc_chttp2_stream **s);
 bool grpc_chttp2_list_remove_writable_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) GRPC_MUST_USE_RESULT;
+    grpc_chttp2_transport *t, grpc_chttp2_stream *s) GRPC_MUST_USE_RESULT;
 
-void grpc_chttp2_list_add_writing_stream(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing);
-int grpc_chttp2_list_have_writing_streams(
-    grpc_chttp2_transport_writing *transport_writing);
-int grpc_chttp2_list_pop_writing_stream(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing **stream_writing);
+bool grpc_chttp2_list_add_writing_stream(grpc_chttp2_transport *t,
+                                         grpc_chttp2_stream *s);
+int grpc_chttp2_list_have_writing_streams(grpc_chttp2_transport *t);
+int grpc_chttp2_list_pop_writing_stream(grpc_chttp2_transport *t,
+                                        grpc_chttp2_stream **s);
 
-void grpc_chttp2_list_add_written_stream(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing);
-int grpc_chttp2_list_pop_written_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_writing **stream_writing);
+void grpc_chttp2_list_add_written_stream(grpc_chttp2_transport *t,
+                                         grpc_chttp2_stream *s);
+int grpc_chttp2_list_pop_written_stream(grpc_chttp2_transport *t,
+                                        grpc_chttp2_stream **s);
 
-void grpc_chttp2_list_add_parsing_seen_stream(
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing);
-int grpc_chttp2_list_pop_parsing_seen_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_parsing **stream_parsing);
+void grpc_chttp2_list_add_waiting_for_concurrency(grpc_chttp2_transport *t,
+                                                  grpc_chttp2_stream *s);
+int grpc_chttp2_list_pop_waiting_for_concurrency(grpc_chttp2_transport *t,
+                                                 grpc_chttp2_stream **s);
 
-void grpc_chttp2_list_add_waiting_for_concurrency(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
-int grpc_chttp2_list_pop_waiting_for_concurrency(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global);
+void grpc_chttp2_list_add_stalled_by_transport(grpc_chttp2_transport *t,
+                                               grpc_chttp2_stream *s);
+int grpc_chttp2_list_pop_stalled_by_transport(grpc_chttp2_transport *t,
+                                              grpc_chttp2_stream **s);
+void grpc_chttp2_list_remove_stalled_by_transport(grpc_chttp2_transport *t,
+                                                  grpc_chttp2_stream *s);
 
-void grpc_chttp2_list_add_check_read_ops(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
-bool grpc_chttp2_list_remove_check_read_ops(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
-int grpc_chttp2_list_pop_check_read_ops(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global);
+grpc_chttp2_stream *grpc_chttp2_parsing_lookup_stream(grpc_chttp2_transport *t,
+                                                      uint32_t id);
+grpc_chttp2_stream *grpc_chttp2_parsing_accept_stream(grpc_exec_ctx *exec_ctx,
+                                                      grpc_chttp2_transport *t,
+                                                      uint32_t id);
 
-void grpc_chttp2_list_add_writing_stalled_by_transport(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing);
-bool grpc_chttp2_list_flush_writing_stalled_by_transport(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing);
+void grpc_chttp2_add_incoming_goaway(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t,
+                                     uint32_t goaway_error,
+                                     gpr_slice goaway_text);
 
-void grpc_chttp2_list_add_stalled_by_transport(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing);
-int grpc_chttp2_list_pop_stalled_by_transport(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global);
-void grpc_chttp2_list_remove_stalled_by_transport(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
+void grpc_chttp2_parsing_become_skip_parser(grpc_exec_ctx *exec_ctx,
+                                            grpc_chttp2_transport *t);
 
-void grpc_chttp2_list_add_unannounced_incoming_window_available(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
-void grpc_chttp2_list_remove_unannounced_incoming_window_available(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
-int grpc_chttp2_list_pop_unannounced_incoming_window_available(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_parsing **stream_parsing);
-
-void grpc_chttp2_list_add_closed_waiting_for_parsing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
-int grpc_chttp2_list_pop_closed_waiting_for_parsing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global);
-
-void grpc_chttp2_list_add_closed_waiting_for_writing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global);
-int grpc_chttp2_list_pop_closed_waiting_for_writing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global);
-
-grpc_chttp2_stream_parsing *grpc_chttp2_parsing_lookup_stream(
-    grpc_chttp2_transport_parsing *transport_parsing, uint32_t id);
-grpc_chttp2_stream_parsing *grpc_chttp2_parsing_accept_stream(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    uint32_t id);
-
-void grpc_chttp2_add_incoming_goaway(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    uint32_t goaway_error, gpr_slice goaway_text);
-
-void grpc_chttp2_register_stream(grpc_chttp2_transport *t,
-                                 grpc_chttp2_stream *s);
-/* returns 1 if this is the last stream, 0 otherwise */
-int grpc_chttp2_unregister_stream(grpc_chttp2_transport *t,
-                                  grpc_chttp2_stream *s) GRPC_MUST_USE_RESULT;
-int grpc_chttp2_has_streams(grpc_chttp2_transport *t);
-void grpc_chttp2_for_all_streams(
-    grpc_chttp2_transport_global *transport_global, void *user_data,
-    void (*cb)(grpc_chttp2_transport_global *transport_global, void *user_data,
-               grpc_chttp2_stream_global *stream_global));
-
-void grpc_chttp2_parsing_become_skip_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-
-void grpc_chttp2_complete_closure_step(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, grpc_closure **pclosure,
-    grpc_error *error);
+void grpc_chttp2_complete_closure_step(grpc_exec_ctx *exec_ctx,
+                                       grpc_chttp2_transport *t,
+                                       grpc_chttp2_stream *s,
+                                       grpc_closure **pclosure,
+                                       grpc_error *error, const char *desc);
 
 #define GRPC_CHTTP2_CLIENT_CONNECT_STRING "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
 #define GRPC_CHTTP2_CLIENT_CONNECT_STRLEN \
@@ -805,57 +591,83 @@
                                const char *var2, int is_client,
                                uint32_t stream_id, int64_t val1, int64_t val2);
 
-void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx,
-                             grpc_chttp2_transport_global *transport_global,
-                             grpc_chttp2_stream_global *stream,
+void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                             grpc_chttp2_stream *stream,
                              grpc_status_code status, gpr_slice *details);
-void grpc_chttp2_mark_stream_closed(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global, int close_reads, int close_writes,
-    grpc_error *error);
+void grpc_chttp2_mark_stream_closed(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_transport *t,
+                                    grpc_chttp2_stream *s, int close_reads,
+                                    int close_writes, grpc_error *error);
 void grpc_chttp2_start_writing(grpc_exec_ctx *exec_ctx,
-                               grpc_chttp2_transport_global *transport_global);
+                               grpc_chttp2_transport *t);
 
 #ifdef GRPC_STREAM_REFCOUNT_DEBUG
-#define GRPC_CHTTP2_STREAM_REF(stream_global, reason) \
-  grpc_chttp2_stream_ref(stream_global, reason)
-#define GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, reason) \
-  grpc_chttp2_stream_unref(exec_ctx, stream_global, reason)
-void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global,
-                            const char *reason);
-void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx,
-                              grpc_chttp2_stream_global *stream_global,
+#define GRPC_CHTTP2_STREAM_REF(stream, reason) \
+  grpc_chttp2_stream_ref(stream, reason)
+#define GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream, reason) \
+  grpc_chttp2_stream_unref(exec_ctx, stream, reason)
+void grpc_chttp2_stream_ref(grpc_chttp2_stream *s, const char *reason);
+void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, grpc_chttp2_stream *s,
                               const char *reason);
 #else
-#define GRPC_CHTTP2_STREAM_REF(stream_global, reason) \
-  grpc_chttp2_stream_ref(stream_global)
-#define GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, reason) \
-  grpc_chttp2_stream_unref(exec_ctx, stream_global)
-void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global);
-void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx,
-                              grpc_chttp2_stream_global *stream_global);
+#define GRPC_CHTTP2_STREAM_REF(stream, reason) grpc_chttp2_stream_ref(stream)
+#define GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream, reason) \
+  grpc_chttp2_stream_unref(exec_ctx, stream)
+void grpc_chttp2_stream_ref(grpc_chttp2_stream *s);
+void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, grpc_chttp2_stream *s);
+#endif
+
+//#define GRPC_CHTTP2_REFCOUNTING_DEBUG 1
+#ifdef GRPC_CHTTP2_REFCOUNTING_DEBUG
+#define GRPC_CHTTP2_REF_TRANSPORT(t, r) \
+  grpc_chttp2_ref_transport(t, r, __FILE__, __LINE__)
+#define GRPC_CHTTP2_UNREF_TRANSPORT(cl, t, r) \
+  grpc_chttp2_unref_transport(cl, t, r, __FILE__, __LINE__)
+void grpc_chttp2_unref_transport(grpc_exec_ctx *exec_ctx,
+                                 grpc_chttp2_transport *t, const char *reason,
+                                 const char *file, int line);
+void grpc_chttp2_ref_transport(grpc_chttp2_transport *t, const char *reason,
+                               const char *file, int line);
+#else
+#define GRPC_CHTTP2_REF_TRANSPORT(t, r) grpc_chttp2_ref_transport(t)
+#define GRPC_CHTTP2_UNREF_TRANSPORT(cl, t, r) grpc_chttp2_unref_transport(cl, t)
+void grpc_chttp2_unref_transport(grpc_exec_ctx *exec_ctx,
+                                 grpc_chttp2_transport *t);
+void grpc_chttp2_ref_transport(grpc_chttp2_transport *t);
 #endif
 
 grpc_chttp2_incoming_byte_stream *grpc_chttp2_incoming_byte_stream_create(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing, uint32_t frame_size,
-    uint32_t flags, grpc_chttp2_incoming_frame_queue *add_to_queue);
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t, grpc_chttp2_stream *s,
+    uint32_t frame_size, uint32_t flags);
 void grpc_chttp2_incoming_byte_stream_push(grpc_exec_ctx *exec_ctx,
                                            grpc_chttp2_incoming_byte_stream *bs,
                                            gpr_slice slice);
 void grpc_chttp2_incoming_byte_stream_finished(
     grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs,
-    grpc_error *error, int from_parsing_thread);
+    grpc_error *error);
 
-void grpc_chttp2_ack_ping(grpc_exec_ctx *exec_ctx,
-                          grpc_chttp2_transport_parsing *parsing,
+void grpc_chttp2_ack_ping(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
                           const uint8_t *opaque_8bytes);
 
 /** add a ref to the stream and add it to the writable list;
     ref will be dropped in writing.c */
 void grpc_chttp2_become_writable(grpc_exec_ctx *exec_ctx,
-                                 grpc_chttp2_transport_global *transport_global,
-                                 grpc_chttp2_stream_global *stream_global,
-                                 bool covered_by_poller, const char *reason);
+                                 grpc_chttp2_transport *t,
+                                 grpc_chttp2_stream *s, bool covered_by_poller,
+                                 const char *reason);
+
+void grpc_chttp2_cancel_stream(grpc_exec_ctx *exec_ctx,
+                               grpc_chttp2_transport *t, grpc_chttp2_stream *s,
+                               grpc_error *due_to_error);
+
+void grpc_chttp2_maybe_complete_recv_initial_metadata(grpc_exec_ctx *exec_ctx,
+                                                      grpc_chttp2_transport *t,
+                                                      grpc_chttp2_stream *s);
+void grpc_chttp2_maybe_complete_recv_message(grpc_exec_ctx *exec_ctx,
+                                             grpc_chttp2_transport *t,
+                                             grpc_chttp2_stream *s);
+void grpc_chttp2_maybe_complete_recv_trailing_metadata(grpc_exec_ctx *exec_ctx,
+                                                       grpc_chttp2_transport *t,
+                                                       grpc_chttp2_stream *s);
 
 #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_INTERNAL_H */
diff --git a/src/core/ext/transport/chttp2/transport/parsing.c b/src/core/ext/transport/chttp2/transport/parsing.c
index 0e6d579..8005350 100644
--- a/src/core/ext/transport/chttp2/transport/parsing.c
+++ b/src/core/ext/transport/chttp2/transport/parsing.c
@@ -45,227 +45,34 @@
 #include "src/core/lib/transport/static_metadata.h"
 #include "src/core/lib/transport/timeout_encoding.h"
 
-#define TRANSPORT_FROM_PARSING(tp)                                        \
-  ((grpc_chttp2_transport *)((char *)(tp)-offsetof(grpc_chttp2_transport, \
-                                                   parsing)))
+static grpc_error *init_frame_parser(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t);
+static grpc_error *init_header_frame_parser(grpc_exec_ctx *exec_ctx,
+                                            grpc_chttp2_transport *t,
+                                            int is_continuation);
+static grpc_error *init_data_frame_parser(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t);
+static grpc_error *init_rst_stream_parser(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t);
+static grpc_error *init_settings_frame_parser(grpc_exec_ctx *exec_ctx,
+                                              grpc_chttp2_transport *t);
+static grpc_error *init_window_update_frame_parser(grpc_exec_ctx *exec_ctx,
+                                                   grpc_chttp2_transport *t);
+static grpc_error *init_ping_parser(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_transport *t);
+static grpc_error *init_goaway_parser(grpc_exec_ctx *exec_ctx,
+                                      grpc_chttp2_transport *t);
+static grpc_error *init_skip_frame_parser(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t,
+                                          int is_header);
 
-static grpc_error *init_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-static grpc_error *init_header_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    int is_continuation);
-static grpc_error *init_data_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-static grpc_error *init_rst_stream_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-static grpc_error *init_settings_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-static grpc_error *init_window_update_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-static grpc_error *init_ping_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-static grpc_error *init_goaway_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing);
-static grpc_error *init_skip_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    int is_header);
+static grpc_error *parse_frame_slice(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t, gpr_slice slice,
+                                     int is_last);
 
-static grpc_error *parse_frame_slice(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    gpr_slice slice, int is_last);
-
-void grpc_chttp2_prepare_to_read(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_parsing *transport_parsing) {
-  grpc_chttp2_stream_global *stream_global;
-  grpc_chttp2_stream_parsing *stream_parsing;
-
-  GPR_TIMER_BEGIN("grpc_chttp2_prepare_to_read", 0);
-
-  transport_parsing->next_stream_id = transport_global->next_stream_id;
-  memcpy(transport_parsing->last_sent_settings,
-         transport_global->settings[GRPC_SENT_SETTINGS],
-         sizeof(transport_parsing->last_sent_settings));
-  transport_parsing->max_frame_size =
-      transport_global->settings[GRPC_ACKED_SETTINGS]
-                                [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE];
-
-  /* update the parsing view of incoming window */
-  while (grpc_chttp2_list_pop_unannounced_incoming_window_available(
-      transport_global, transport_parsing, &stream_global, &stream_parsing)) {
-    GRPC_CHTTP2_FLOW_MOVE_STREAM("parse", transport_parsing, stream_parsing,
-                                 incoming_window, stream_global,
-                                 unannounced_incoming_window_for_parse);
-  }
-
-  GPR_TIMER_END("grpc_chttp2_prepare_to_read", 0);
-}
-
-void grpc_chttp2_publish_reads(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_parsing *transport_parsing) {
-  grpc_chttp2_stream_global *stream_global;
-  grpc_chttp2_stream_parsing *stream_parsing;
-  int was_zero;
-  int is_zero;
-
-  /* transport_parsing->last_incoming_stream_id is used as
-     last-grpc_chttp2_stream-id when
-     sending GOAWAY frame.
-     https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-6.8
-     says that last-grpc_chttp2_stream-id is peer-initiated grpc_chttp2_stream
-     ID.  So,
-     since we don't have server pushed streams, client should send
-     GOAWAY last-grpc_chttp2_stream-id=0 in this case. */
-  if (!transport_parsing->is_client) {
-    transport_global->last_incoming_stream_id =
-        transport_parsing->last_incoming_stream_id;
-  }
-
-  /* update global settings */
-  if (transport_parsing->settings_updated) {
-    memcpy(transport_global->settings[GRPC_PEER_SETTINGS],
-           transport_parsing->settings, sizeof(transport_parsing->settings));
-    transport_parsing->settings_updated = 0;
-  }
-
-  /* update settings based on ack if received */
-  if (transport_parsing->settings_ack_received) {
-    memcpy(transport_global->settings[GRPC_ACKED_SETTINGS],
-           transport_global->settings[GRPC_SENT_SETTINGS],
-           GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t));
-    transport_parsing->settings_ack_received = 0;
-    transport_global->sent_local_settings = 0;
-  }
-
-  /* move goaway to the global state if we received one (it will be
-     published later */
-  if (transport_parsing->goaway_received) {
-    grpc_chttp2_add_incoming_goaway(exec_ctx, transport_global,
-                                    (uint32_t)transport_parsing->goaway_error,
-                                    transport_parsing->goaway_text);
-    transport_parsing->goaway_text = gpr_empty_slice();
-    transport_parsing->goaway_received = 0;
-  }
-
-  /* propagate flow control tokens to global state */
-  was_zero = transport_global->outgoing_window <= 0;
-  GRPC_CHTTP2_FLOW_MOVE_TRANSPORT("parsed", transport_global, outgoing_window,
-                                  transport_parsing, outgoing_window);
-  is_zero = transport_global->outgoing_window <= 0;
-  if (was_zero && !is_zero) {
-    grpc_chttp2_initiate_write(exec_ctx, transport_global, false,
-                               "new_global_flow_control");
-  }
-
-  if (transport_parsing->incoming_window <
-      transport_global->connection_window_target * 3 / 4) {
-    int64_t announce_bytes = transport_global->connection_window_target -
-                             transport_parsing->incoming_window;
-    GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parsed", transport_global,
-                                      announce_incoming_window, announce_bytes);
-    GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parsed", transport_parsing,
-                                      incoming_window, announce_bytes);
-    grpc_chttp2_initiate_write(exec_ctx, transport_global, false,
-                               "global incoming window");
-  }
-
-  /* for each stream that saw an update, fixup global state */
-  while (grpc_chttp2_list_pop_parsing_seen_stream(
-      transport_global, transport_parsing, &stream_global, &stream_parsing)) {
-    if (stream_parsing->seen_error) {
-      stream_global->seen_error = true;
-      stream_global->exceeded_metadata_size =
-          stream_parsing->exceeded_metadata_size;
-      grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                          stream_global);
-    }
-
-    /* flush stats to global stream state */
-    grpc_transport_move_stats(&stream_parsing->stats, &stream_global->stats);
-
-    /* update outgoing flow control window */
-    was_zero = stream_global->outgoing_window <= 0;
-    GRPC_CHTTP2_FLOW_MOVE_STREAM("parsed", transport_global, stream_global,
-                                 outgoing_window, stream_parsing,
-                                 outgoing_window);
-    is_zero = stream_global->outgoing_window <= 0;
-    if (was_zero && !is_zero) {
-      grpc_chttp2_become_writable(exec_ctx, transport_global, stream_global,
-                                  false, "stream.read_flow_control");
-    }
-
-    stream_global->max_recv_bytes -= (uint32_t)GPR_MIN(
-        stream_global->max_recv_bytes, stream_parsing->received_bytes);
-    stream_parsing->received_bytes = 0;
-
-    /* publish incoming stream ops */
-    if (stream_global->incoming_frames.tail != NULL) {
-      stream_global->incoming_frames.tail->is_tail = 0;
-    }
-    if (stream_parsing->data_parser.incoming_frames.head != NULL) {
-      grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                          stream_global);
-    }
-    grpc_chttp2_incoming_frame_queue_merge(
-        &stream_global->incoming_frames,
-        &stream_parsing->data_parser.incoming_frames);
-    if (stream_global->incoming_frames.tail != NULL) {
-      stream_global->incoming_frames.tail->is_tail = 1;
-    }
-
-    if (!stream_global->published_initial_metadata &&
-        stream_parsing->got_metadata_on_parse[0]) {
-      stream_parsing->got_metadata_on_parse[0] = 0;
-      stream_global->published_initial_metadata = 1;
-      GPR_SWAP(grpc_chttp2_incoming_metadata_buffer,
-               stream_parsing->metadata_buffer[0],
-               stream_global->received_initial_metadata);
-      grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                          stream_global);
-    }
-    if (!stream_global->published_trailing_metadata &&
-        stream_parsing->got_metadata_on_parse[1]) {
-      stream_parsing->got_metadata_on_parse[1] = 0;
-      stream_global->published_trailing_metadata = 1;
-      GPR_SWAP(grpc_chttp2_incoming_metadata_buffer,
-               stream_parsing->metadata_buffer[1],
-               stream_global->received_trailing_metadata);
-      grpc_chttp2_list_add_check_read_ops(exec_ctx, transport_global,
-                                          stream_global);
-    }
-
-    if (stream_parsing->forced_close_error != GRPC_ERROR_NONE) {
-      intptr_t reason;
-      bool has_reason = grpc_error_get_int(stream_parsing->forced_close_error,
-                                           GRPC_ERROR_INT_HTTP2_ERROR, &reason);
-      if (has_reason && reason != GRPC_CHTTP2_NO_ERROR) {
-        grpc_status_code status_code =
-            has_reason
-                ? grpc_chttp2_http2_error_to_grpc_status(
-                      (grpc_chttp2_error_code)reason, stream_global->deadline)
-                : GRPC_STATUS_INTERNAL;
-        const char *status_details =
-            grpc_error_string(stream_parsing->forced_close_error);
-        gpr_slice slice_details = gpr_slice_from_copied_string(status_details);
-        grpc_error_free_string(status_details);
-        grpc_chttp2_fake_status(exec_ctx, transport_global, stream_global,
-                                status_code, &slice_details);
-      }
-      grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global,
-                                     1, 1, stream_parsing->forced_close_error);
-    }
-
-    if (stream_parsing->received_close) {
-      grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global,
-                                     1, 0, GRPC_ERROR_NONE);
-    }
-  }
-}
-
-grpc_error *grpc_chttp2_perform_read(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    gpr_slice slice) {
+grpc_error *grpc_chttp2_perform_read(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t,
+                                     gpr_slice slice) {
   uint8_t *beg = GPR_SLICE_START_PTR(slice);
   uint8_t *end = GPR_SLICE_END_PTR(slice);
   uint8_t *cur = beg;
@@ -273,7 +80,7 @@
 
   if (cur == end) return GRPC_ERROR_NONE;
 
-  switch (transport_parsing->deframe_state) {
+  switch (t->deframe_state) {
     case GRPC_DTS_CLIENT_PREFIX_0:
     case GRPC_DTS_CLIENT_PREFIX_1:
     case GRPC_DTS_CLIENT_PREFIX_2:
@@ -298,25 +105,22 @@
     case GRPC_DTS_CLIENT_PREFIX_21:
     case GRPC_DTS_CLIENT_PREFIX_22:
     case GRPC_DTS_CLIENT_PREFIX_23:
-      while (cur != end && transport_parsing->deframe_state != GRPC_DTS_FH_0) {
-        if (*cur != GRPC_CHTTP2_CLIENT_CONNECT_STRING[transport_parsing
-                                                          ->deframe_state]) {
+      while (cur != end && t->deframe_state != GRPC_DTS_FH_0) {
+        if (*cur != GRPC_CHTTP2_CLIENT_CONNECT_STRING[t->deframe_state]) {
           char *msg;
           gpr_asprintf(
               &msg,
               "Connect string mismatch: expected '%c' (%d) got '%c' (%d) "
               "at byte %d",
-              GRPC_CHTTP2_CLIENT_CONNECT_STRING[transport_parsing
-                                                    ->deframe_state],
-              (int)(uint8_t)GRPC_CHTTP2_CLIENT_CONNECT_STRING
-                  [transport_parsing->deframe_state],
-              *cur, (int)*cur, transport_parsing->deframe_state);
+              GRPC_CHTTP2_CLIENT_CONNECT_STRING[t->deframe_state],
+              (int)(uint8_t)GRPC_CHTTP2_CLIENT_CONNECT_STRING[t->deframe_state],
+              *cur, (int)*cur, t->deframe_state);
           err = GRPC_ERROR_CREATE(msg);
           gpr_free(msg);
           return err;
         }
         ++cur;
-        ++transport_parsing->deframe_state;
+        ++t->deframe_state;
       }
       if (cur == end) {
         return GRPC_ERROR_NONE;
@@ -325,100 +129,95 @@
     dts_fh_0:
     case GRPC_DTS_FH_0:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_frame_size = ((uint32_t)*cur) << 16;
+      t->incoming_frame_size = ((uint32_t)*cur) << 16;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_1;
+        t->deframe_state = GRPC_DTS_FH_1;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_1:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_frame_size |= ((uint32_t)*cur) << 8;
+      t->incoming_frame_size |= ((uint32_t)*cur) << 8;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_2;
+        t->deframe_state = GRPC_DTS_FH_2;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_2:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_frame_size |= *cur;
+      t->incoming_frame_size |= *cur;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_3;
+        t->deframe_state = GRPC_DTS_FH_3;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_3:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_frame_type = *cur;
+      t->incoming_frame_type = *cur;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_4;
+        t->deframe_state = GRPC_DTS_FH_4;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_4:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_frame_flags = *cur;
+      t->incoming_frame_flags = *cur;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_5;
+        t->deframe_state = GRPC_DTS_FH_5;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_5:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_stream_id = (((uint32_t)*cur) & 0x7f) << 24;
+      t->incoming_stream_id = (((uint32_t)*cur) & 0x7f) << 24;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_6;
+        t->deframe_state = GRPC_DTS_FH_6;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_6:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_stream_id |= ((uint32_t)*cur) << 16;
+      t->incoming_stream_id |= ((uint32_t)*cur) << 16;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_7;
+        t->deframe_state = GRPC_DTS_FH_7;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_7:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_stream_id |= ((uint32_t)*cur) << 8;
+      t->incoming_stream_id |= ((uint32_t)*cur) << 8;
       if (++cur == end) {
-        transport_parsing->deframe_state = GRPC_DTS_FH_8;
+        t->deframe_state = GRPC_DTS_FH_8;
         return GRPC_ERROR_NONE;
       }
     /* fallthrough */
     case GRPC_DTS_FH_8:
       GPR_ASSERT(cur < end);
-      transport_parsing->incoming_stream_id |= ((uint32_t)*cur);
-      transport_parsing->deframe_state = GRPC_DTS_FRAME;
-      err = init_frame_parser(exec_ctx, transport_parsing);
+      t->incoming_stream_id |= ((uint32_t)*cur);
+      t->deframe_state = GRPC_DTS_FRAME;
+      err = init_frame_parser(exec_ctx, t);
       if (err != GRPC_ERROR_NONE) {
         return err;
       }
-      if (transport_parsing->incoming_stream_id != 0 &&
-          transport_parsing->incoming_stream_id >
-              transport_parsing->last_incoming_stream_id) {
-        transport_parsing->last_incoming_stream_id =
-            transport_parsing->incoming_stream_id;
-      }
-      if (transport_parsing->incoming_frame_size == 0) {
-        err = parse_frame_slice(exec_ctx, transport_parsing, gpr_empty_slice(),
-                                1);
+      if (t->incoming_frame_size == 0) {
+        err = parse_frame_slice(exec_ctx, t, gpr_empty_slice(), 1);
         if (err != GRPC_ERROR_NONE) {
           return err;
         }
-        transport_parsing->incoming_stream = NULL;
+        t->incoming_stream = NULL;
         if (++cur == end) {
-          transport_parsing->deframe_state = GRPC_DTS_FH_0;
+          t->deframe_state = GRPC_DTS_FH_0;
           return GRPC_ERROR_NONE;
         }
         goto dts_fh_0; /* loop */
-      } else if (transport_parsing->incoming_frame_size >
-                 transport_parsing->max_frame_size) {
+      } else if (t->incoming_frame_size >
+                 t->settings[GRPC_ACKED_SETTINGS]
+                            [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE]) {
         char *msg;
         gpr_asprintf(&msg, "Frame size %d is larger than max frame size %d",
-                     transport_parsing->incoming_frame_size,
-                     transport_parsing->max_frame_size);
+                     t->incoming_frame_size,
+                     t->settings[GRPC_ACKED_SETTINGS]
+                                [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE]);
         err = GRPC_ERROR_CREATE(msg);
         gpr_free(msg);
         return err;
@@ -429,41 +228,39 @@
     /* fallthrough */
     case GRPC_DTS_FRAME:
       GPR_ASSERT(cur < end);
-      if ((uint32_t)(end - cur) == transport_parsing->incoming_frame_size) {
-        err = parse_frame_slice(exec_ctx, transport_parsing,
+      if ((uint32_t)(end - cur) == t->incoming_frame_size) {
+        err = parse_frame_slice(exec_ctx, t,
                                 gpr_slice_sub_no_ref(slice, (size_t)(cur - beg),
                                                      (size_t)(end - beg)),
                                 1);
         if (err != GRPC_ERROR_NONE) {
           return err;
         }
-        transport_parsing->deframe_state = GRPC_DTS_FH_0;
-        transport_parsing->incoming_stream = NULL;
+        t->deframe_state = GRPC_DTS_FH_0;
+        t->incoming_stream = NULL;
         return GRPC_ERROR_NONE;
-      } else if ((uint32_t)(end - cur) >
-                 transport_parsing->incoming_frame_size) {
+      } else if ((uint32_t)(end - cur) > t->incoming_frame_size) {
         size_t cur_offset = (size_t)(cur - beg);
         err = parse_frame_slice(
-            exec_ctx, transport_parsing,
-            gpr_slice_sub_no_ref(
-                slice, cur_offset,
-                cur_offset + transport_parsing->incoming_frame_size),
+            exec_ctx, t,
+            gpr_slice_sub_no_ref(slice, cur_offset,
+                                 cur_offset + t->incoming_frame_size),
             1);
         if (err != GRPC_ERROR_NONE) {
           return err;
         }
-        cur += transport_parsing->incoming_frame_size;
-        transport_parsing->incoming_stream = NULL;
+        cur += t->incoming_frame_size;
+        t->incoming_stream = NULL;
         goto dts_fh_0; /* loop */
       } else {
-        err = parse_frame_slice(exec_ctx, transport_parsing,
+        err = parse_frame_slice(exec_ctx, t,
                                 gpr_slice_sub_no_ref(slice, (size_t)(cur - beg),
                                                      (size_t)(end - beg)),
                                 0);
         if (err != GRPC_ERROR_NONE) {
           return err;
         }
-        transport_parsing->incoming_frame_size -= (uint32_t)(end - cur);
+        t->incoming_frame_size -= (uint32_t)(end - cur);
         return GRPC_ERROR_NONE;
       }
       GPR_UNREACHABLE_CODE(return 0);
@@ -472,175 +269,172 @@
   GPR_UNREACHABLE_CODE(return 0);
 }
 
-static grpc_error *init_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
-  if (transport_parsing->is_first_frame &&
-      transport_parsing->incoming_frame_type != GRPC_CHTTP2_FRAME_SETTINGS) {
+static grpc_error *init_frame_parser(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t) {
+  if (t->is_first_frame &&
+      t->incoming_frame_type != GRPC_CHTTP2_FRAME_SETTINGS) {
     char *msg;
     gpr_asprintf(
         &msg, "Expected SETTINGS frame as the first frame, got frame type %d",
-        transport_parsing->incoming_frame_type);
+        t->incoming_frame_type);
     grpc_error *err = GRPC_ERROR_CREATE(msg);
     gpr_free(msg);
     return err;
   }
-  transport_parsing->is_first_frame = false;
-  if (transport_parsing->expect_continuation_stream_id != 0) {
-    if (transport_parsing->incoming_frame_type !=
-        GRPC_CHTTP2_FRAME_CONTINUATION) {
+  t->is_first_frame = false;
+  if (t->expect_continuation_stream_id != 0) {
+    if (t->incoming_frame_type != GRPC_CHTTP2_FRAME_CONTINUATION) {
       char *msg;
       gpr_asprintf(&msg, "Expected CONTINUATION frame, got frame type %02x",
-                   transport_parsing->incoming_frame_type);
+                   t->incoming_frame_type);
       grpc_error *err = GRPC_ERROR_CREATE(msg);
       gpr_free(msg);
       return err;
     }
-    if (transport_parsing->expect_continuation_stream_id !=
-        transport_parsing->incoming_stream_id) {
+    if (t->expect_continuation_stream_id != t->incoming_stream_id) {
       char *msg;
       gpr_asprintf(
           &msg,
           "Expected CONTINUATION frame for grpc_chttp2_stream %08x, got "
           "grpc_chttp2_stream %08x",
-          transport_parsing->expect_continuation_stream_id,
-          transport_parsing->incoming_stream_id);
+          t->expect_continuation_stream_id, t->incoming_stream_id);
       grpc_error *err = GRPC_ERROR_CREATE(msg);
       gpr_free(msg);
       return err;
     }
-    return init_header_frame_parser(exec_ctx, transport_parsing, 1);
+    return init_header_frame_parser(exec_ctx, t, 1);
   }
-  switch (transport_parsing->incoming_frame_type) {
+  switch (t->incoming_frame_type) {
     case GRPC_CHTTP2_FRAME_DATA:
-      return init_data_frame_parser(exec_ctx, transport_parsing);
+      return init_data_frame_parser(exec_ctx, t);
     case GRPC_CHTTP2_FRAME_HEADER:
-      return init_header_frame_parser(exec_ctx, transport_parsing, 0);
+      return init_header_frame_parser(exec_ctx, t, 0);
     case GRPC_CHTTP2_FRAME_CONTINUATION:
       return GRPC_ERROR_CREATE("Unexpected CONTINUATION frame");
     case GRPC_CHTTP2_FRAME_RST_STREAM:
-      return init_rst_stream_parser(exec_ctx, transport_parsing);
+      return init_rst_stream_parser(exec_ctx, t);
     case GRPC_CHTTP2_FRAME_SETTINGS:
-      return init_settings_frame_parser(exec_ctx, transport_parsing);
+      return init_settings_frame_parser(exec_ctx, t);
     case GRPC_CHTTP2_FRAME_WINDOW_UPDATE:
-      return init_window_update_frame_parser(exec_ctx, transport_parsing);
+      return init_window_update_frame_parser(exec_ctx, t);
     case GRPC_CHTTP2_FRAME_PING:
-      return init_ping_parser(exec_ctx, transport_parsing);
+      return init_ping_parser(exec_ctx, t);
     case GRPC_CHTTP2_FRAME_GOAWAY:
-      return init_goaway_parser(exec_ctx, transport_parsing);
+      return init_goaway_parser(exec_ctx, t);
     default:
       if (grpc_http_trace) {
-        gpr_log(GPR_ERROR, "Unknown frame type %02x",
-                transport_parsing->incoming_frame_type);
+        gpr_log(GPR_ERROR, "Unknown frame type %02x", t->incoming_frame_type);
       }
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 0);
+      return init_skip_frame_parser(exec_ctx, t, 0);
   }
 }
 
 static grpc_error *skip_parser(grpc_exec_ctx *exec_ctx, void *parser,
-                               grpc_chttp2_transport_parsing *transport_parsing,
-                               grpc_chttp2_stream_parsing *stream_parsing,
+                               grpc_chttp2_transport *t, grpc_chttp2_stream *s,
                                gpr_slice slice, int is_last) {
   return GRPC_ERROR_NONE;
 }
 
-static void skip_header(void *tp, grpc_mdelem *md) { GRPC_MDELEM_UNREF(md); }
+static void skip_header(grpc_exec_ctx *exec_ctx, void *tp, grpc_mdelem *md) {
+  GRPC_MDELEM_UNREF(md);
+}
 
-static grpc_error *init_skip_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    int is_header) {
+static grpc_error *init_skip_frame_parser(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t,
+                                          int is_header) {
   if (is_header) {
-    uint8_t is_eoh = transport_parsing->expect_continuation_stream_id != 0;
-    transport_parsing->parser = grpc_chttp2_header_parser_parse;
-    transport_parsing->parser_data = &transport_parsing->hpack_parser;
-    transport_parsing->hpack_parser.on_header = skip_header;
-    transport_parsing->hpack_parser.on_header_user_data = NULL;
-    transport_parsing->hpack_parser.is_boundary = is_eoh;
-    transport_parsing->hpack_parser.is_eof =
-        (uint8_t)(is_eoh ? transport_parsing->header_eof : 0);
+    uint8_t is_eoh = t->expect_continuation_stream_id != 0;
+    t->parser = grpc_chttp2_header_parser_parse;
+    t->parser_data = &t->hpack_parser;
+    t->hpack_parser.on_header = skip_header;
+    t->hpack_parser.on_header_user_data = NULL;
+    t->hpack_parser.is_boundary = is_eoh;
+    t->hpack_parser.is_eof = (uint8_t)(is_eoh ? t->header_eof : 0);
   } else {
-    transport_parsing->parser = skip_parser;
+    t->parser = skip_parser;
   }
   return GRPC_ERROR_NONE;
 }
 
-void grpc_chttp2_parsing_become_skip_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
-  init_skip_frame_parser(
-      exec_ctx, transport_parsing,
-      transport_parsing->parser == grpc_chttp2_header_parser_parse);
+void grpc_chttp2_parsing_become_skip_parser(grpc_exec_ctx *exec_ctx,
+                                            grpc_chttp2_transport *t) {
+  init_skip_frame_parser(exec_ctx, t,
+                         t->parser == grpc_chttp2_header_parser_parse);
 }
 
-static grpc_error *update_incoming_window(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing) {
-  uint32_t incoming_frame_size = transport_parsing->incoming_frame_size;
-  if (incoming_frame_size > transport_parsing->incoming_window) {
+static grpc_error *update_incoming_window(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s) {
+  uint32_t incoming_frame_size = t->incoming_frame_size;
+  if (incoming_frame_size > t->incoming_window) {
     char *msg;
     gpr_asprintf(&msg, "frame of size %d overflows incoming window of %" PRId64,
-                 transport_parsing->incoming_frame_size,
-                 transport_parsing->incoming_window);
+                 t->incoming_frame_size, t->incoming_window);
     grpc_error *err = GRPC_ERROR_CREATE(msg);
     gpr_free(msg);
     return err;
   }
 
-  if (incoming_frame_size > stream_parsing->incoming_window) {
-    char *msg;
-    gpr_asprintf(&msg, "frame of size %d overflows incoming window of %" PRId64,
-                 transport_parsing->incoming_frame_size,
-                 stream_parsing->incoming_window);
-    grpc_error *err = GRPC_ERROR_CREATE(msg);
-    gpr_free(msg);
-    return err;
-  }
-
-  GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("parse", transport_parsing, incoming_window,
+  GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("parse", t, incoming_window,
                                    incoming_frame_size);
-  GRPC_CHTTP2_FLOW_DEBIT_STREAM("parse", transport_parsing, stream_parsing,
-                                incoming_window, incoming_frame_size);
-  stream_parsing->received_bytes += incoming_frame_size;
 
-  grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, stream_parsing);
+  if (s != NULL) {
+    if (incoming_frame_size > s->incoming_window) {
+      char *msg;
+      gpr_asprintf(&msg,
+                   "frame of size %d overflows incoming window of %" PRId64,
+                   t->incoming_frame_size, s->incoming_window);
+      grpc_error *err = GRPC_ERROR_CREATE(msg);
+      gpr_free(msg);
+      return err;
+    }
+
+    GRPC_CHTTP2_FLOW_DEBIT_STREAM("parse", t, s, incoming_window,
+                                  incoming_frame_size);
+    s->received_bytes += incoming_frame_size;
+    s->max_recv_bytes -=
+        (uint32_t)GPR_MIN(s->max_recv_bytes, incoming_frame_size);
+  }
 
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *init_data_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
-  grpc_chttp2_stream_parsing *stream_parsing =
-      grpc_chttp2_parsing_lookup_stream(transport_parsing,
-                                        transport_parsing->incoming_stream_id);
+static grpc_error *init_data_frame_parser(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t) {
+  grpc_chttp2_stream *s =
+      grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
   grpc_error *err = GRPC_ERROR_NONE;
-  if (stream_parsing == NULL) {
-    return init_skip_frame_parser(exec_ctx, transport_parsing, 0);
+  err = update_incoming_window(exec_ctx, t, s);
+  if (err != GRPC_ERROR_NONE) {
+    goto error_handler;
   }
-  stream_parsing->stats.incoming.framing_bytes += 9;
-  if (stream_parsing->received_close) {
-    return init_skip_frame_parser(exec_ctx, transport_parsing, 0);
+  if (s == NULL) {
+    return init_skip_frame_parser(exec_ctx, t, 0);
+  }
+  s->stats.incoming.framing_bytes += 9;
+  if (err == GRPC_ERROR_NONE && s->read_closed) {
+    return init_skip_frame_parser(exec_ctx, t, 0);
   }
   if (err == GRPC_ERROR_NONE) {
-    err = update_incoming_window(exec_ctx, transport_parsing, stream_parsing);
+    err = grpc_chttp2_data_parser_begin_frame(&s->data_parser,
+                                              t->incoming_frame_flags, s->id);
   }
+error_handler:
   if (err == GRPC_ERROR_NONE) {
-    err = grpc_chttp2_data_parser_begin_frame(
-        &stream_parsing->data_parser, transport_parsing->incoming_frame_flags,
-        stream_parsing->id);
-  }
-  if (err == GRPC_ERROR_NONE) {
-    transport_parsing->incoming_stream = stream_parsing;
-    transport_parsing->parser = grpc_chttp2_data_parser_parse;
-    transport_parsing->parser_data = &stream_parsing->data_parser;
+    t->incoming_stream = s;
+    t->parser = grpc_chttp2_data_parser_parse;
+    t->parser_data = &s->data_parser;
     return GRPC_ERROR_NONE;
   } else if (grpc_error_get_int(err, GRPC_ERROR_INT_STREAM_ID, NULL)) {
     /* handle stream errors by closing the stream */
-    stream_parsing->received_close = 1;
-    stream_parsing->forced_close_error = err;
+    if (s != NULL) {
+      grpc_chttp2_mark_stream_closed(exec_ctx, t, s, true, false, err);
+    }
     gpr_slice_buffer_add(
-        &transport_parsing->qbuf,
-        grpc_chttp2_rst_stream_create(transport_parsing->incoming_stream_id,
-                                      GRPC_CHTTP2_PROTOCOL_ERROR,
-                                      &stream_parsing->stats.outgoing));
-    return init_skip_frame_parser(exec_ctx, transport_parsing, 0);
+        &t->qbuf, grpc_chttp2_rst_stream_create(t->incoming_stream_id,
+                                                GRPC_CHTTP2_PROTOCOL_ERROR,
+                                                &s->stats.outgoing));
+    return init_skip_frame_parser(exec_ctx, t, 0);
   } else {
     return err;
   }
@@ -648,23 +442,22 @@
 
 static void free_timeout(void *p) { gpr_free(p); }
 
-static void on_initial_header(void *tp, grpc_mdelem *md) {
-  grpc_chttp2_transport_parsing *transport_parsing = tp;
-  grpc_chttp2_stream_parsing *stream_parsing =
-      transport_parsing->incoming_stream;
+static void on_initial_header(grpc_exec_ctx *exec_ctx, void *tp,
+                              grpc_mdelem *md) {
+  grpc_chttp2_transport *t = tp;
+  grpc_chttp2_stream *s = t->incoming_stream;
 
   GPR_TIMER_BEGIN("on_initial_header", 0);
 
-  GPR_ASSERT(stream_parsing);
+  GPR_ASSERT(s != NULL);
 
   GRPC_CHTTP2_IF_TRACING(gpr_log(
-      GPR_INFO, "HTTP:%d:HDR:%s: %s: %s", stream_parsing->id,
-      transport_parsing->is_client ? "CLI" : "SVR",
+      GPR_INFO, "HTTP:%d:HDR:%s: %s: %s", s->id, t->is_client ? "CLI" : "SVR",
       grpc_mdstr_as_c_string(md->key), grpc_mdstr_as_c_string(md->value)));
 
   if (md->key == GRPC_MDSTR_GRPC_STATUS && md != GRPC_MDELEM_GRPC_STATUS_0) {
     /* TODO(ctiller): check for a status like " 0" */
-    stream_parsing->seen_error = true;
+    s->seen_error = true;
   }
 
   if (md->key == GRPC_MDSTR_GRPC_TIMEOUT) {
@@ -681,306 +474,273 @@
       grpc_mdelem_set_user_data(md, free_timeout, cached_timeout);
     }
     grpc_chttp2_incoming_metadata_buffer_set_deadline(
-        &stream_parsing->metadata_buffer[0],
+        &s->metadata_buffer[0],
         gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), *cached_timeout));
     GRPC_MDELEM_UNREF(md);
   } else {
-    const size_t new_size =
-        stream_parsing->metadata_buffer[0].size + GRPC_MDELEM_LENGTH(md);
-    grpc_chttp2_transport_global *transport_global =
-        &TRANSPORT_FROM_PARSING(transport_parsing)->global;
+    const size_t new_size = s->metadata_buffer[0].size + GRPC_MDELEM_LENGTH(md);
     const size_t metadata_size_limit =
-        transport_global->settings[GRPC_LOCAL_SETTINGS]
-                                  [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
+        t->settings[GRPC_ACKED_SETTINGS]
+                   [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
     if (new_size > metadata_size_limit) {
-      if (!stream_parsing->exceeded_metadata_size) {
-        gpr_log(GPR_DEBUG,
-                "received initial metadata size exceeds limit (%" PRIuPTR
-                " vs. %" PRIuPTR ")",
-                new_size, metadata_size_limit);
-        stream_parsing->seen_error = true;
-        stream_parsing->exceeded_metadata_size = true;
-      }
+      gpr_log(GPR_DEBUG,
+              "received initial metadata size exceeds limit (%" PRIuPTR
+              " vs. %" PRIuPTR ")",
+              new_size, metadata_size_limit);
+      grpc_chttp2_cancel_stream(
+          exec_ctx, t, s,
+          grpc_error_set_int(
+              GRPC_ERROR_CREATE("received initial metadata size exceeds limit"),
+              GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_RESOURCE_EXHAUSTED));
+      grpc_chttp2_parsing_become_skip_parser(exec_ctx, t);
+      s->seen_error = true;
       GRPC_MDELEM_UNREF(md);
     } else {
-      grpc_chttp2_incoming_metadata_buffer_add(
-          &stream_parsing->metadata_buffer[0], md);
+      grpc_chttp2_incoming_metadata_buffer_add(&s->metadata_buffer[0], md);
     }
   }
 
-  grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, stream_parsing);
-
   GPR_TIMER_END("on_initial_header", 0);
 }
 
-static void on_trailing_header(void *tp, grpc_mdelem *md) {
-  grpc_chttp2_transport_parsing *transport_parsing = tp;
-  grpc_chttp2_stream_parsing *stream_parsing =
-      transport_parsing->incoming_stream;
+static void on_trailing_header(grpc_exec_ctx *exec_ctx, void *tp,
+                               grpc_mdelem *md) {
+  grpc_chttp2_transport *t = tp;
+  grpc_chttp2_stream *s = t->incoming_stream;
 
   GPR_TIMER_BEGIN("on_trailing_header", 0);
 
-  GPR_ASSERT(stream_parsing);
+  GPR_ASSERT(s != NULL);
 
   GRPC_CHTTP2_IF_TRACING(gpr_log(
-      GPR_INFO, "HTTP:%d:TRL:%s: %s: %s", stream_parsing->id,
-      transport_parsing->is_client ? "CLI" : "SVR",
+      GPR_INFO, "HTTP:%d:TRL:%s: %s: %s", s->id, t->is_client ? "CLI" : "SVR",
       grpc_mdstr_as_c_string(md->key), grpc_mdstr_as_c_string(md->value)));
 
   if (md->key == GRPC_MDSTR_GRPC_STATUS && md != GRPC_MDELEM_GRPC_STATUS_0) {
     /* TODO(ctiller): check for a status like " 0" */
-    stream_parsing->seen_error = true;
+    s->seen_error = true;
   }
 
-  const size_t new_size =
-      stream_parsing->metadata_buffer[1].size + GRPC_MDELEM_LENGTH(md);
-  grpc_chttp2_transport_global *transport_global =
-      &TRANSPORT_FROM_PARSING(transport_parsing)->global;
+  const size_t new_size = s->metadata_buffer[1].size + GRPC_MDELEM_LENGTH(md);
   const size_t metadata_size_limit =
-      transport_global->settings[GRPC_LOCAL_SETTINGS]
-                                [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
+      t->settings[GRPC_ACKED_SETTINGS]
+                 [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE];
   if (new_size > metadata_size_limit) {
-    if (!stream_parsing->exceeded_metadata_size) {
-      gpr_log(GPR_DEBUG,
-              "received trailing metadata size exceeds limit (%" PRIuPTR
-              " vs. %" PRIuPTR ")",
-              new_size, metadata_size_limit);
-      stream_parsing->seen_error = true;
-      stream_parsing->exceeded_metadata_size = true;
-    }
+    gpr_log(GPR_DEBUG,
+            "received trailing metadata size exceeds limit (%" PRIuPTR
+            " vs. %" PRIuPTR ")",
+            new_size, metadata_size_limit);
+    grpc_chttp2_cancel_stream(
+        exec_ctx, t, s,
+        grpc_error_set_int(
+            GRPC_ERROR_CREATE("received trailing metadata size exceeds limit"),
+            GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_RESOURCE_EXHAUSTED));
+    grpc_chttp2_parsing_become_skip_parser(exec_ctx, t);
+    s->seen_error = true;
     GRPC_MDELEM_UNREF(md);
   } else {
-    grpc_chttp2_incoming_metadata_buffer_add(
-        &stream_parsing->metadata_buffer[1], md);
+    grpc_chttp2_incoming_metadata_buffer_add(&s->metadata_buffer[1], md);
   }
 
-  grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, stream_parsing);
-
   GPR_TIMER_END("on_trailing_header", 0);
 }
 
-static grpc_error *init_header_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    int is_continuation) {
-  uint8_t is_eoh = (transport_parsing->incoming_frame_flags &
-                    GRPC_CHTTP2_DATA_FLAG_END_HEADERS) != 0;
-  int via_accept = 0;
-  grpc_chttp2_stream_parsing *stream_parsing;
+static grpc_error *init_header_frame_parser(grpc_exec_ctx *exec_ctx,
+                                            grpc_chttp2_transport *t,
+                                            int is_continuation) {
+  uint8_t is_eoh =
+      (t->incoming_frame_flags & GRPC_CHTTP2_DATA_FLAG_END_HEADERS) != 0;
+  grpc_chttp2_stream *s;
 
   /* TODO(ctiller): when to increment header_frames_received? */
 
   if (is_eoh) {
-    transport_parsing->expect_continuation_stream_id = 0;
+    t->expect_continuation_stream_id = 0;
   } else {
-    transport_parsing->expect_continuation_stream_id =
-        transport_parsing->incoming_stream_id;
+    t->expect_continuation_stream_id = t->incoming_stream_id;
   }
 
   if (!is_continuation) {
-    transport_parsing->header_eof = (transport_parsing->incoming_frame_flags &
-                                     GRPC_CHTTP2_DATA_FLAG_END_STREAM) != 0;
+    t->header_eof =
+        (t->incoming_frame_flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM) != 0;
   }
 
   /* could be a new grpc_chttp2_stream or an existing grpc_chttp2_stream */
-  stream_parsing = grpc_chttp2_parsing_lookup_stream(
-      transport_parsing, transport_parsing->incoming_stream_id);
-  if (stream_parsing == NULL) {
+  s = grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
+  if (s == NULL) {
     if (is_continuation) {
-      gpr_log(GPR_ERROR,
-              "grpc_chttp2_stream disbanded before CONTINUATION received");
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 1);
+      GRPC_CHTTP2_IF_TRACING(
+          gpr_log(GPR_ERROR,
+                  "grpc_chttp2_stream disbanded before CONTINUATION received"));
+      return init_skip_frame_parser(exec_ctx, t, 1);
     }
-    if (transport_parsing->is_client) {
-      if ((transport_parsing->incoming_stream_id & 1) &&
-          transport_parsing->incoming_stream_id <
-              transport_parsing->next_stream_id) {
+    if (t->is_client) {
+      if ((t->incoming_stream_id & 1) &&
+          t->incoming_stream_id < t->next_stream_id) {
         /* this is an old (probably cancelled) grpc_chttp2_stream */
       } else {
-        gpr_log(GPR_ERROR,
-                "ignoring new grpc_chttp2_stream creation on client");
+        GRPC_CHTTP2_IF_TRACING(gpr_log(
+            GPR_ERROR, "ignoring new grpc_chttp2_stream creation on client"));
       }
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 1);
-    } else if (transport_parsing->last_incoming_stream_id >
-               transport_parsing->incoming_stream_id) {
-      gpr_log(GPR_ERROR,
-              "ignoring out of order new grpc_chttp2_stream request on server; "
-              "last grpc_chttp2_stream "
-              "id=%d, new grpc_chttp2_stream id=%d",
-              transport_parsing->last_incoming_stream_id,
-              transport_parsing->incoming_stream_id);
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 1);
-    } else if ((transport_parsing->incoming_stream_id & 1) == 0) {
-      gpr_log(GPR_ERROR,
-              "ignoring grpc_chttp2_stream with non-client generated index %d",
-              transport_parsing->incoming_stream_id);
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 1);
+      return init_skip_frame_parser(exec_ctx, t, 1);
+    } else if (t->last_new_stream_id >= t->incoming_stream_id) {
+      GRPC_CHTTP2_IF_TRACING(gpr_log(
+          GPR_ERROR,
+          "ignoring out of order new grpc_chttp2_stream request on server; "
+          "last grpc_chttp2_stream "
+          "id=%d, new grpc_chttp2_stream id=%d",
+          t->last_new_stream_id, t->incoming_stream_id));
+      return init_skip_frame_parser(exec_ctx, t, 1);
+    } else if ((t->incoming_stream_id & 1) == 0) {
+      GRPC_CHTTP2_IF_TRACING(gpr_log(
+          GPR_ERROR,
+          "ignoring grpc_chttp2_stream with non-client generated index %d",
+          t->incoming_stream_id));
+      return init_skip_frame_parser(exec_ctx, t, 1);
     }
-    stream_parsing = transport_parsing->incoming_stream =
-        grpc_chttp2_parsing_accept_stream(
-            exec_ctx, transport_parsing, transport_parsing->incoming_stream_id);
-    if (stream_parsing == NULL) {
-      gpr_log(GPR_ERROR, "grpc_chttp2_stream not accepted");
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 1);
+    t->last_new_stream_id = t->incoming_stream_id;
+    s = t->incoming_stream =
+        grpc_chttp2_parsing_accept_stream(exec_ctx, t, t->incoming_stream_id);
+    if (s == NULL) {
+      GRPC_CHTTP2_IF_TRACING(
+          gpr_log(GPR_ERROR, "grpc_chttp2_stream not accepted"));
+      return init_skip_frame_parser(exec_ctx, t, 1);
     }
-    via_accept = 1;
   } else {
-    transport_parsing->incoming_stream = stream_parsing;
+    t->incoming_stream = s;
   }
-  GPR_ASSERT(stream_parsing != NULL && (via_accept == 0 || via_accept == 1));
-  stream_parsing->stats.incoming.framing_bytes += 9;
-  if (stream_parsing->received_close) {
-    gpr_log(GPR_ERROR, "skipping already closed grpc_chttp2_stream header");
-    transport_parsing->incoming_stream = NULL;
-    return init_skip_frame_parser(exec_ctx, transport_parsing, 1);
+  GPR_ASSERT(s != NULL);
+  s->stats.incoming.framing_bytes += 9;
+  if (s->read_closed) {
+    GRPC_CHTTP2_IF_TRACING(gpr_log(
+        GPR_ERROR, "skipping already closed grpc_chttp2_stream header"));
+    t->incoming_stream = NULL;
+    return init_skip_frame_parser(exec_ctx, t, 1);
   }
-  transport_parsing->parser = grpc_chttp2_header_parser_parse;
-  transport_parsing->parser_data = &transport_parsing->hpack_parser;
-  switch (stream_parsing->header_frames_received) {
+  t->parser = grpc_chttp2_header_parser_parse;
+  t->parser_data = &t->hpack_parser;
+  switch (s->header_frames_received) {
     case 0:
-      transport_parsing->hpack_parser.on_header = on_initial_header;
+      t->hpack_parser.on_header = on_initial_header;
       break;
     case 1:
-      transport_parsing->hpack_parser.on_header = on_trailing_header;
+      t->hpack_parser.on_header = on_trailing_header;
       break;
     case 2:
       gpr_log(GPR_ERROR, "too many header frames received");
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 1);
+      return init_skip_frame_parser(exec_ctx, t, 1);
   }
-  transport_parsing->hpack_parser.on_header_user_data = transport_parsing;
-  transport_parsing->hpack_parser.is_boundary = is_eoh;
-  transport_parsing->hpack_parser.is_eof =
-      (uint8_t)(is_eoh ? transport_parsing->header_eof : 0);
-  if (!is_continuation && (transport_parsing->incoming_frame_flags &
-                           GRPC_CHTTP2_FLAG_HAS_PRIORITY)) {
-    grpc_chttp2_hpack_parser_set_has_priority(&transport_parsing->hpack_parser);
+  t->hpack_parser.on_header_user_data = t;
+  t->hpack_parser.is_boundary = is_eoh;
+  t->hpack_parser.is_eof = (uint8_t)(is_eoh ? t->header_eof : 0);
+  if (!is_continuation &&
+      (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_HAS_PRIORITY)) {
+    grpc_chttp2_hpack_parser_set_has_priority(&t->hpack_parser);
   }
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *init_window_update_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
+static grpc_error *init_window_update_frame_parser(grpc_exec_ctx *exec_ctx,
+                                                   grpc_chttp2_transport *t) {
   grpc_error *err = grpc_chttp2_window_update_parser_begin_frame(
-      &transport_parsing->simple.window_update,
-      transport_parsing->incoming_frame_size,
-      transport_parsing->incoming_frame_flags);
+      &t->simple.window_update, t->incoming_frame_size,
+      t->incoming_frame_flags);
   if (err != GRPC_ERROR_NONE) return err;
-  if (transport_parsing->incoming_stream_id != 0) {
-    grpc_chttp2_stream_parsing *stream_parsing =
-        transport_parsing->incoming_stream = grpc_chttp2_parsing_lookup_stream(
-            transport_parsing, transport_parsing->incoming_stream_id);
-    if (stream_parsing == NULL) {
-      return init_skip_frame_parser(exec_ctx, transport_parsing, 0);
+  if (t->incoming_stream_id != 0) {
+    grpc_chttp2_stream *s = t->incoming_stream =
+        grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
+    if (s == NULL) {
+      return init_skip_frame_parser(exec_ctx, t, 0);
     }
-    stream_parsing->stats.incoming.framing_bytes += 9;
+    s->stats.incoming.framing_bytes += 9;
   }
-  transport_parsing->parser = grpc_chttp2_window_update_parser_parse;
-  transport_parsing->parser_data = &transport_parsing->simple.window_update;
+  t->parser = grpc_chttp2_window_update_parser_parse;
+  t->parser_data = &t->simple.window_update;
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *init_ping_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
+static grpc_error *init_ping_parser(grpc_exec_ctx *exec_ctx,
+                                    grpc_chttp2_transport *t) {
   grpc_error *err = grpc_chttp2_ping_parser_begin_frame(
-      &transport_parsing->simple.ping, transport_parsing->incoming_frame_size,
-      transport_parsing->incoming_frame_flags);
+      &t->simple.ping, t->incoming_frame_size, t->incoming_frame_flags);
   if (err != GRPC_ERROR_NONE) return err;
-  transport_parsing->parser = grpc_chttp2_ping_parser_parse;
-  transport_parsing->parser_data = &transport_parsing->simple.ping;
+  t->parser = grpc_chttp2_ping_parser_parse;
+  t->parser_data = &t->simple.ping;
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *init_rst_stream_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
+static grpc_error *init_rst_stream_parser(grpc_exec_ctx *exec_ctx,
+                                          grpc_chttp2_transport *t) {
   grpc_error *err = grpc_chttp2_rst_stream_parser_begin_frame(
-      &transport_parsing->simple.rst_stream,
-      transport_parsing->incoming_frame_size,
-      transport_parsing->incoming_frame_flags);
+      &t->simple.rst_stream, t->incoming_frame_size, t->incoming_frame_flags);
   if (err != GRPC_ERROR_NONE) return err;
-  grpc_chttp2_stream_parsing *stream_parsing =
-      transport_parsing->incoming_stream = grpc_chttp2_parsing_lookup_stream(
-          transport_parsing, transport_parsing->incoming_stream_id);
-  if (!transport_parsing->incoming_stream) {
-    return init_skip_frame_parser(exec_ctx, transport_parsing, 0);
+  grpc_chttp2_stream *s = t->incoming_stream =
+      grpc_chttp2_parsing_lookup_stream(t, t->incoming_stream_id);
+  if (!t->incoming_stream) {
+    return init_skip_frame_parser(exec_ctx, t, 0);
   }
-  stream_parsing->stats.incoming.framing_bytes += 9;
-  transport_parsing->parser = grpc_chttp2_rst_stream_parser_parse;
-  transport_parsing->parser_data = &transport_parsing->simple.rst_stream;
+  s->stats.incoming.framing_bytes += 9;
+  t->parser = grpc_chttp2_rst_stream_parser_parse;
+  t->parser_data = &t->simple.rst_stream;
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *init_goaway_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
+static grpc_error *init_goaway_parser(grpc_exec_ctx *exec_ctx,
+                                      grpc_chttp2_transport *t) {
   grpc_error *err = grpc_chttp2_goaway_parser_begin_frame(
-      &transport_parsing->goaway_parser, transport_parsing->incoming_frame_size,
-      transport_parsing->incoming_frame_flags);
+      &t->goaway_parser, t->incoming_frame_size, t->incoming_frame_flags);
   if (err != GRPC_ERROR_NONE) return err;
-  transport_parsing->parser = grpc_chttp2_goaway_parser_parse;
-  transport_parsing->parser_data = &transport_parsing->goaway_parser;
+  t->parser = grpc_chttp2_goaway_parser_parse;
+  t->parser_data = &t->goaway_parser;
   return GRPC_ERROR_NONE;
 }
 
-static grpc_error *init_settings_frame_parser(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) {
-  if (transport_parsing->incoming_stream_id != 0) {
+static grpc_error *init_settings_frame_parser(grpc_exec_ctx *exec_ctx,
+                                              grpc_chttp2_transport *t) {
+  if (t->incoming_stream_id != 0) {
     return GRPC_ERROR_CREATE("Settings frame received for grpc_chttp2_stream");
   }
 
   grpc_error *err = grpc_chttp2_settings_parser_begin_frame(
-      &transport_parsing->simple.settings,
-      transport_parsing->incoming_frame_size,
-      transport_parsing->incoming_frame_flags, transport_parsing->settings);
+      &t->simple.settings, t->incoming_frame_size, t->incoming_frame_flags,
+      t->settings[GRPC_PEER_SETTINGS]);
   if (err != GRPC_ERROR_NONE) {
     return err;
   }
-  if (transport_parsing->incoming_frame_flags & GRPC_CHTTP2_FLAG_ACK) {
-    transport_parsing->settings_ack_received = 1;
+  if (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_ACK) {
+    memcpy(t->settings[GRPC_ACKED_SETTINGS], t->settings[GRPC_SENT_SETTINGS],
+           GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t));
     grpc_chttp2_hptbl_set_max_bytes(
-        &transport_parsing->hpack_parser.table,
-        transport_parsing
-            ->last_sent_settings[GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
-    transport_parsing->max_frame_size =
-        transport_parsing
-            ->last_sent_settings[GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE];
+        &t->hpack_parser.table,
+        t->settings[GRPC_ACKED_SETTINGS]
+                   [GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
+    t->sent_local_settings = 0;
   }
-  transport_parsing->parser = grpc_chttp2_settings_parser_parse;
-  transport_parsing->parser_data = &transport_parsing->simple.settings;
+  t->parser = grpc_chttp2_settings_parser_parse;
+  t->parser_data = &t->simple.settings;
   return GRPC_ERROR_NONE;
 }
 
-/*
-static int is_window_update_legal(int64_t window_update, int64_t window) {
-  return window + window_update < MAX_WINDOW;
-}
-*/
-
-static grpc_error *parse_frame_slice(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing,
-    gpr_slice slice, int is_last) {
-  grpc_chttp2_stream_parsing *stream_parsing =
-      transport_parsing->incoming_stream;
-  grpc_error *err = transport_parsing->parser(
-      exec_ctx, transport_parsing->parser_data, transport_parsing,
-      stream_parsing, slice, is_last);
+static grpc_error *parse_frame_slice(grpc_exec_ctx *exec_ctx,
+                                     grpc_chttp2_transport *t, gpr_slice slice,
+                                     int is_last) {
+  grpc_chttp2_stream *s = t->incoming_stream;
+  grpc_error *err = t->parser(exec_ctx, t->parser_data, t, s, slice, is_last);
   if (err == GRPC_ERROR_NONE) {
-    if (stream_parsing) {
-      grpc_chttp2_list_add_parsing_seen_stream(transport_parsing,
-                                               stream_parsing);
-    }
-    return GRPC_ERROR_NONE;
+    return err;
   } else if (grpc_error_get_int(err, GRPC_ERROR_INT_STREAM_ID, NULL)) {
     if (grpc_http_trace) {
       const char *msg = grpc_error_string(err);
       gpr_log(GPR_ERROR, "%s", msg);
       grpc_error_free_string(msg);
     }
-    grpc_chttp2_parsing_become_skip_parser(exec_ctx, transport_parsing);
-    if (stream_parsing) {
-      stream_parsing->forced_close_error = err;
+    grpc_chttp2_parsing_become_skip_parser(exec_ctx, t);
+    if (s) {
+      s->forced_close_error = err;
       gpr_slice_buffer_add(
-          &transport_parsing->qbuf,
-          grpc_chttp2_rst_stream_create(transport_parsing->incoming_stream_id,
-                                        GRPC_CHTTP2_PROTOCOL_ERROR,
-                                        &stream_parsing->stats.outgoing));
+          &t->qbuf, grpc_chttp2_rst_stream_create(t->incoming_stream_id,
+                                                  GRPC_CHTTP2_PROTOCOL_ERROR,
+                                                  &s->stats.outgoing));
     } else {
       GRPC_ERROR_UNREF(err);
     }
diff --git a/src/core/ext/transport/chttp2/transport/stream_lists.c b/src/core/ext/transport/chttp2/transport/stream_lists.c
index 4dc4968..6d25b3a 100644
--- a/src/core/ext/transport/chttp2/transport/stream_lists.c
+++ b/src/core/ext/transport/chttp2/transport/stream_lists.c
@@ -35,27 +35,6 @@
 
 #include <grpc/support/log.h>
 
-#define TRANSPORT_FROM_GLOBAL(tg)                                         \
-  ((grpc_chttp2_transport *)((char *)(tg)-offsetof(grpc_chttp2_transport, \
-                                                   global)))
-
-#define STREAM_FROM_GLOBAL(sg) \
-  ((grpc_chttp2_stream *)((char *)(sg)-offsetof(grpc_chttp2_stream, global)))
-
-#define TRANSPORT_FROM_WRITING(tw)                                        \
-  ((grpc_chttp2_transport *)((char *)(tw)-offsetof(grpc_chttp2_transport, \
-                                                   writing)))
-
-#define STREAM_FROM_WRITING(sw) \
-  ((grpc_chttp2_stream *)((char *)(sw)-offsetof(grpc_chttp2_stream, writing)))
-
-#define TRANSPORT_FROM_PARSING(tp)                                        \
-  ((grpc_chttp2_transport *)((char *)(tp)-offsetof(grpc_chttp2_transport, \
-                                                   parsing)))
-
-#define STREAM_FROM_PARSING(sp) \
-  ((grpc_chttp2_stream *)((char *)(sp)-offsetof(grpc_chttp2_stream, parsing)))
-
 /* core list management */
 
 static int stream_list_empty(grpc_chttp2_transport *t,
@@ -139,321 +118,57 @@
 
 /* wrappers for specializations */
 
-bool grpc_chttp2_list_add_writable_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  GPR_ASSERT(stream_global->id != 0);
-  return stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global),
-                         STREAM_FROM_GLOBAL(stream_global),
-                         GRPC_CHTTP2_LIST_WRITABLE);
+bool grpc_chttp2_list_add_writable_stream(grpc_chttp2_transport *t,
+                                          grpc_chttp2_stream *s) {
+  GPR_ASSERT(s->id != 0);
+  return stream_list_add(t, s, GRPC_CHTTP2_LIST_WRITABLE);
 }
 
-int grpc_chttp2_list_pop_writable_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_writing **stream_writing) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
-                          GRPC_CHTTP2_LIST_WRITABLE);
-  if (r != 0) {
-    *stream_global = &stream->global;
-    *stream_writing = &stream->writing;
-  }
-  return r;
+int grpc_chttp2_list_pop_writable_stream(grpc_chttp2_transport *t,
+                                         grpc_chttp2_stream **s) {
+  return stream_list_pop(t, s, GRPC_CHTTP2_LIST_WRITABLE);
 }
 
-bool grpc_chttp2_list_remove_writable_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  return stream_list_maybe_remove(TRANSPORT_FROM_GLOBAL(transport_global),
-                                  STREAM_FROM_GLOBAL(stream_global),
-                                  GRPC_CHTTP2_LIST_WRITABLE);
+bool grpc_chttp2_list_remove_writable_stream(grpc_chttp2_transport *t,
+                                             grpc_chttp2_stream *s) {
+  return stream_list_maybe_remove(t, s, GRPC_CHTTP2_LIST_WRITABLE);
 }
 
-void grpc_chttp2_list_add_writing_stream(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing) {
-  GPR_ASSERT(stream_list_add(TRANSPORT_FROM_WRITING(transport_writing),
-                             STREAM_FROM_WRITING(stream_writing),
-                             GRPC_CHTTP2_LIST_WRITING));
+bool grpc_chttp2_list_add_writing_stream(grpc_chttp2_transport *t,
+                                         grpc_chttp2_stream *s) {
+  return stream_list_add(t, s, GRPC_CHTTP2_LIST_WRITING);
 }
 
-int grpc_chttp2_list_have_writing_streams(
-    grpc_chttp2_transport_writing *transport_writing) {
-  return !stream_list_empty(TRANSPORT_FROM_WRITING(transport_writing),
-                            GRPC_CHTTP2_LIST_WRITING);
+int grpc_chttp2_list_have_writing_streams(grpc_chttp2_transport *t) {
+  return !stream_list_empty(t, GRPC_CHTTP2_LIST_WRITING);
 }
 
-int grpc_chttp2_list_pop_writing_stream(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing **stream_writing) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_WRITING(transport_writing), &stream,
-                          GRPC_CHTTP2_LIST_WRITING);
-  if (r != 0) {
-    *stream_writing = &stream->writing;
-  }
-  return r;
+int grpc_chttp2_list_pop_writing_stream(grpc_chttp2_transport *t,
+                                        grpc_chttp2_stream **s) {
+  return stream_list_pop(t, s, GRPC_CHTTP2_LIST_WRITING);
 }
 
-void grpc_chttp2_list_add_written_stream(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing) {
-  stream_list_add(TRANSPORT_FROM_WRITING(transport_writing),
-                  STREAM_FROM_WRITING(stream_writing),
-                  GRPC_CHTTP2_LIST_WRITTEN);
+void grpc_chttp2_list_add_waiting_for_concurrency(grpc_chttp2_transport *t,
+                                                  grpc_chttp2_stream *s) {
+  stream_list_add(t, s, GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY);
 }
 
-int grpc_chttp2_list_pop_written_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_writing **stream_writing) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_WRITING(transport_writing), &stream,
-                          GRPC_CHTTP2_LIST_WRITTEN);
-  if (r != 0) {
-    *stream_global = &stream->global;
-    *stream_writing = &stream->writing;
-  }
-  return r;
+int grpc_chttp2_list_pop_waiting_for_concurrency(grpc_chttp2_transport *t,
+                                                 grpc_chttp2_stream **s) {
+  return stream_list_pop(t, s, GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY);
 }
 
-void grpc_chttp2_list_add_unannounced_incoming_window_available(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  GPR_ASSERT(stream_global->id != 0);
-  stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global),
-                  STREAM_FROM_GLOBAL(stream_global),
-                  GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE);
+void grpc_chttp2_list_add_stalled_by_transport(grpc_chttp2_transport *t,
+                                               grpc_chttp2_stream *s) {
+  stream_list_add(t, s, GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT);
 }
 
-void grpc_chttp2_list_remove_unannounced_incoming_window_available(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  stream_list_maybe_remove(
-      TRANSPORT_FROM_GLOBAL(transport_global),
-      STREAM_FROM_GLOBAL(stream_global),
-      GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE);
+int grpc_chttp2_list_pop_stalled_by_transport(grpc_chttp2_transport *t,
+                                              grpc_chttp2_stream **s) {
+  return stream_list_pop(t, s, GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT);
 }
 
-int grpc_chttp2_list_pop_unannounced_incoming_window_available(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_parsing **stream_parsing) {
-  grpc_chttp2_stream *stream;
-  int r =
-      stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
-                      GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE);
-  if (r != 0) {
-    *stream_global = &stream->global;
-    *stream_parsing = &stream->parsing;
-  }
-  return r;
-}
-
-void grpc_chttp2_list_add_parsing_seen_stream(
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_parsing *stream_parsing) {
-  stream_list_add(TRANSPORT_FROM_PARSING(transport_parsing),
-                  STREAM_FROM_PARSING(stream_parsing),
-                  GRPC_CHTTP2_LIST_PARSING_SEEN);
-}
-
-int grpc_chttp2_list_pop_parsing_seen_stream(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_parsing *transport_parsing,
-    grpc_chttp2_stream_global **stream_global,
-    grpc_chttp2_stream_parsing **stream_parsing) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_PARSING(transport_parsing), &stream,
-                          GRPC_CHTTP2_LIST_PARSING_SEEN);
-  if (r != 0) {
-    *stream_global = &stream->global;
-    *stream_parsing = &stream->parsing;
-  }
-  return r;
-}
-
-void grpc_chttp2_list_add_waiting_for_concurrency(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global),
-                  STREAM_FROM_GLOBAL(stream_global),
-                  GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY);
-}
-
-int grpc_chttp2_list_pop_waiting_for_concurrency(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
-                          GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY);
-  if (r != 0) {
-    *stream_global = &stream->global;
-  }
-  return r;
-}
-
-void grpc_chttp2_list_add_check_read_ops(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  grpc_chttp2_transport *t = TRANSPORT_FROM_GLOBAL(transport_global);
-  if (!t->executor.check_read_ops_scheduled) {
-    grpc_combiner_execute_finally(exec_ctx, t->executor.combiner,
-                                  &t->initiate_read_flush_locked,
-                                  GRPC_ERROR_NONE, false);
-    t->executor.check_read_ops_scheduled = true;
-  }
-  stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global),
-                  STREAM_FROM_GLOBAL(stream_global),
-                  GRPC_CHTTP2_LIST_CHECK_READ_OPS);
-}
-
-bool grpc_chttp2_list_remove_check_read_ops(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  return stream_list_maybe_remove(TRANSPORT_FROM_GLOBAL(transport_global),
-                                  STREAM_FROM_GLOBAL(stream_global),
-                                  GRPC_CHTTP2_LIST_CHECK_READ_OPS);
-}
-
-int grpc_chttp2_list_pop_check_read_ops(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
-                          GRPC_CHTTP2_LIST_CHECK_READ_OPS);
-  if (r != 0) {
-    *stream_global = &stream->global;
-  }
-  return r;
-}
-
-void grpc_chttp2_list_add_writing_stalled_by_transport(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing) {
-  grpc_chttp2_stream *stream = STREAM_FROM_WRITING(stream_writing);
-  gpr_log(GPR_DEBUG, "writing stalled %d", stream->global.id);
-  if (!stream->included[GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT]) {
-    GRPC_CHTTP2_STREAM_REF(&stream->global, "chttp2_writing_stalled");
-  }
-  stream_list_add(TRANSPORT_FROM_WRITING(transport_writing), stream,
-                  GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT);
-}
-
-bool grpc_chttp2_list_flush_writing_stalled_by_transport(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing) {
-  grpc_chttp2_stream *stream;
-  bool out = false;
-  grpc_chttp2_transport *transport = TRANSPORT_FROM_WRITING(transport_writing);
-  while (stream_list_pop(transport, &stream,
-                         GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT)) {
-    gpr_log(GPR_DEBUG, "move %d from writing stalled to just stalled",
-            stream->global.id);
-    grpc_chttp2_list_add_stalled_by_transport(transport_writing,
-                                              &stream->writing);
-    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, &stream->global,
-                             "chttp2_writing_stalled");
-    out = true;
-  }
-  return out;
-}
-
-void grpc_chttp2_list_add_stalled_by_transport(
-    grpc_chttp2_transport_writing *transport_writing,
-    grpc_chttp2_stream_writing *stream_writing) {
-  gpr_log(GPR_DEBUG, "stalled %d", stream_writing->id);
-  stream_list_add(TRANSPORT_FROM_WRITING(transport_writing),
-                  STREAM_FROM_WRITING(stream_writing),
-                  GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT);
-}
-
-int grpc_chttp2_list_pop_stalled_by_transport(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
-                          GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT);
-  if (r != 0) {
-    *stream_global = &stream->global;
-  }
-  return r;
-}
-
-void grpc_chttp2_list_remove_stalled_by_transport(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  stream_list_maybe_remove(TRANSPORT_FROM_GLOBAL(transport_global),
-                           STREAM_FROM_GLOBAL(stream_global),
-                           GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT);
-}
-
-void grpc_chttp2_list_add_closed_waiting_for_parsing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global),
-                  STREAM_FROM_GLOBAL(stream_global),
-                  GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_PARSING);
-}
-
-int grpc_chttp2_list_pop_closed_waiting_for_parsing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
-                          GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_PARSING);
-  if (r != 0) {
-    *stream_global = &stream->global;
-  }
-  return r;
-}
-
-void grpc_chttp2_list_add_closed_waiting_for_writing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global *stream_global) {
-  stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global),
-                  STREAM_FROM_GLOBAL(stream_global),
-                  GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_WRITING);
-}
-
-int grpc_chttp2_list_pop_closed_waiting_for_writing(
-    grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_stream_global **stream_global) {
-  grpc_chttp2_stream *stream;
-  int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream,
-                          GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_WRITING);
-  if (r != 0) {
-    *stream_global = &stream->global;
-  }
-  return r;
-}
-
-void grpc_chttp2_register_stream(grpc_chttp2_transport *t,
-                                 grpc_chttp2_stream *s) {
-  stream_list_add_tail(t, s, GRPC_CHTTP2_LIST_ALL_STREAMS);
-}
-
-int grpc_chttp2_unregister_stream(grpc_chttp2_transport *t,
-                                  grpc_chttp2_stream *s) {
-  stream_list_maybe_remove(t, s, GRPC_CHTTP2_LIST_ALL_STREAMS);
-  return stream_list_empty(t, GRPC_CHTTP2_LIST_ALL_STREAMS);
-}
-
-int grpc_chttp2_has_streams(grpc_chttp2_transport *t) {
-  return !stream_list_empty(t, GRPC_CHTTP2_LIST_ALL_STREAMS);
-}
-
-void grpc_chttp2_for_all_streams(
-    grpc_chttp2_transport_global *transport_global, void *user_data,
-    void (*cb)(grpc_chttp2_transport_global *transport_global, void *user_data,
-               grpc_chttp2_stream_global *stream_global)) {
-  grpc_chttp2_stream *s;
-  grpc_chttp2_transport *t = TRANSPORT_FROM_GLOBAL(transport_global);
-  for (s = t->lists[GRPC_CHTTP2_LIST_ALL_STREAMS].head; s != NULL;
-       s = s->links[GRPC_CHTTP2_LIST_ALL_STREAMS].next) {
-    cb(transport_global, user_data, &s->global);
-  }
+void grpc_chttp2_list_remove_stalled_by_transport(grpc_chttp2_transport *t,
+                                                  grpc_chttp2_stream *s) {
+  stream_list_maybe_remove(t, s, GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT);
 }
diff --git a/src/core/ext/transport/chttp2/transport/stream_map.c b/src/core/ext/transport/chttp2/transport/stream_map.c
index f70791c..59b3a14 100644
--- a/src/core/ext/transport/chttp2/transport/stream_map.c
+++ b/src/core/ext/transport/chttp2/transport/stream_map.c
@@ -77,6 +77,7 @@
 
   GPR_ASSERT(count == 0 || keys[count - 1] < key);
   GPR_ASSERT(value);
+  GPR_ASSERT(grpc_chttp2_stream_map_find(map, key) == NULL);
 
   if (count == capacity) {
     if (map->free > capacity / 4) {
@@ -96,40 +97,6 @@
   map->count = count + 1;
 }
 
-void grpc_chttp2_stream_map_move_into(grpc_chttp2_stream_map *src,
-                                      grpc_chttp2_stream_map *dst) {
-  /* if src is empty we dont need to do anything */
-  if (src->count == src->free) {
-    return;
-  }
-  /* if dst is empty we simply need to swap */
-  if (dst->count == dst->free) {
-    GPR_SWAP(grpc_chttp2_stream_map, *src, *dst);
-    return;
-  }
-  /* the first element of src must be greater than the last of dst...
-   * however the maps may need compacting for this property to hold */
-  if (src->keys[0] <= dst->keys[dst->count - 1]) {
-    src->count = compact(src->keys, src->values, src->count);
-    src->free = 0;
-    dst->count = compact(dst->keys, dst->values, dst->count);
-    dst->free = 0;
-  }
-  GPR_ASSERT(src->keys[0] > dst->keys[dst->count - 1]);
-  /* if dst doesn't have capacity, resize */
-  if (dst->count + src->count > dst->capacity) {
-    dst->capacity = GPR_MAX(dst->capacity * 3 / 2, dst->count + src->count);
-    dst->keys = gpr_realloc(dst->keys, dst->capacity * sizeof(uint32_t));
-    dst->values = gpr_realloc(dst->values, dst->capacity * sizeof(void *));
-  }
-  memcpy(dst->keys + dst->count, src->keys, src->count * sizeof(uint32_t));
-  memcpy(dst->values + dst->count, src->values, src->count * sizeof(void *));
-  dst->count += src->count;
-  dst->free += src->free;
-  src->count = 0;
-  src->free = 0;
-}
-
 static void **find(grpc_chttp2_stream_map *map, uint32_t key) {
   size_t min_idx = 0;
   size_t max_idx = map->count;
@@ -170,6 +137,7 @@
     if (map->free == map->count) {
       map->free = map->count = 0;
     }
+    GPR_ASSERT(grpc_chttp2_stream_map_find(map, key) == NULL);
   }
   return out;
 }
diff --git a/src/core/ext/transport/chttp2/transport/stream_map.h b/src/core/ext/transport/chttp2/transport/stream_map.h
index b1d59ca..e76312d 100644
--- a/src/core/ext/transport/chttp2/transport/stream_map.h
+++ b/src/core/ext/transport/chttp2/transport/stream_map.h
@@ -65,10 +65,6 @@
    or NULL otherwise */
 void *grpc_chttp2_stream_map_delete(grpc_chttp2_stream_map *map, uint32_t key);
 
-/* Move all elements of src into dst */
-void grpc_chttp2_stream_map_move_into(grpc_chttp2_stream_map *src,
-                                      grpc_chttp2_stream_map *dst);
-
 /* Return an existing key, or NULL if it does not exist */
 void *grpc_chttp2_stream_map_find(grpc_chttp2_stream_map *map, uint32_t key);
 
diff --git a/src/core/ext/transport/chttp2/transport/writing.c b/src/core/ext/transport/chttp2/transport/writing.c
index 979515b..b39695a 100644
--- a/src/core/ext/transport/chttp2/transport/writing.c
+++ b/src/core/ext/transport/chttp2/transport/writing.c
@@ -40,349 +40,221 @@
 #include "src/core/ext/transport/chttp2/transport/http2_errors.h"
 #include "src/core/lib/profiling/timers.h"
 
-static void finalize_outbuf(grpc_exec_ctx *exec_ctx,
-                            grpc_chttp2_transport_writing *transport_writing);
+static void add_to_write_list(grpc_chttp2_write_cb **list,
+                              grpc_chttp2_write_cb *cb) {
+  cb->next = *list;
+  *list = cb;
+}
 
-int grpc_chttp2_unlocking_check_writes(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_writing *transport_writing) {
-  grpc_chttp2_stream_global *stream_global;
-  grpc_chttp2_stream_writing *stream_writing;
+static void finish_write_cb(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                            grpc_chttp2_stream *s, grpc_chttp2_write_cb *cb,
+                            grpc_error *error) {
+  grpc_chttp2_complete_closure_step(exec_ctx, t, s, &cb->closure, error,
+                                    "finish_write_cb");
+  cb->next = t->write_cb_pool;
+  t->write_cb_pool = cb;
+}
 
-  GPR_TIMER_BEGIN("grpc_chttp2_unlocking_check_writes", 0);
+static void update_list(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                        grpc_chttp2_stream *s, int64_t send_bytes,
+                        grpc_chttp2_write_cb **list, grpc_error *error) {
+  grpc_chttp2_write_cb *cb = *list;
+  *list = NULL;
+  s->flow_controlled_bytes_written += send_bytes;
+  while (cb) {
+    grpc_chttp2_write_cb *next = cb->next;
+    if (cb->call_at_byte <= s->flow_controlled_bytes_written) {
+      finish_write_cb(exec_ctx, t, s, cb, GRPC_ERROR_REF(error));
+    } else {
+      add_to_write_list(list, cb);
+    }
+    cb = next;
+  }
+  GRPC_ERROR_UNREF(error);
+}
 
-  transport_writing->max_frame_size =
-      transport_global->settings[GRPC_ACKED_SETTINGS]
-                                [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE];
+bool grpc_chttp2_begin_write(grpc_exec_ctx *exec_ctx,
+                             grpc_chttp2_transport *t) {
+  grpc_chttp2_stream *s;
 
-  if (transport_global->dirtied_local_settings &&
-      !transport_global->sent_local_settings) {
+  GPR_TIMER_BEGIN("grpc_chttp2_begin_write", 0);
+
+  if (t->dirtied_local_settings && !t->sent_local_settings) {
     gpr_slice_buffer_add(
-        &transport_writing->outbuf,
+        &t->outbuf,
         grpc_chttp2_settings_create(
-            transport_global->settings[GRPC_SENT_SETTINGS],
-            transport_global->settings[GRPC_LOCAL_SETTINGS],
-            transport_global->force_send_settings, GRPC_CHTTP2_NUM_SETTINGS));
-    transport_global->force_send_settings = 0;
-    transport_global->dirtied_local_settings = 0;
-    transport_global->sent_local_settings = 1;
+            t->settings[GRPC_SENT_SETTINGS], t->settings[GRPC_LOCAL_SETTINGS],
+            t->force_send_settings, GRPC_CHTTP2_NUM_SETTINGS));
+    t->force_send_settings = 0;
+    t->dirtied_local_settings = 0;
+    t->sent_local_settings = 1;
   }
 
   /* simple writes are queued to qbuf, and flushed here */
-  gpr_slice_buffer_move_into(&transport_global->qbuf,
-                             &transport_writing->outbuf);
-  GPR_ASSERT(transport_global->qbuf.count == 0);
+  gpr_slice_buffer_move_into(&t->qbuf, &t->outbuf);
+  GPR_ASSERT(t->qbuf.count == 0);
 
   grpc_chttp2_hpack_compressor_set_max_table_size(
-      &transport_writing->hpack_compressor,
-      transport_global->settings[GRPC_PEER_SETTINGS]
-                                [GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
+      &t->hpack_compressor,
+      t->settings[GRPC_PEER_SETTINGS][GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
 
-  GRPC_CHTTP2_FLOW_MOVE_TRANSPORT("write", transport_writing, outgoing_window,
-                                  transport_global, outgoing_window);
-  if (transport_writing->outgoing_window > 0) {
-    while (grpc_chttp2_list_pop_stalled_by_transport(transport_global,
-                                                     &stream_global)) {
-      grpc_chttp2_become_writable(exec_ctx, transport_global, stream_global,
-                                  false, "transport.read_flow_control");
+  if (t->outgoing_window > 0) {
+    while (grpc_chttp2_list_pop_stalled_by_transport(t, &s)) {
+      grpc_chttp2_become_writable(exec_ctx, t, s, false,
+                                  "transport.read_flow_control");
     }
   }
 
   /* for each grpc_chttp2_stream that's become writable, frame it's data
      (according to available window sizes) and add to the output buffer */
-  while (grpc_chttp2_list_pop_writable_stream(
-      transport_global, transport_writing, &stream_global, &stream_writing)) {
-    bool sent_initial_metadata = stream_writing->sent_initial_metadata;
-    bool become_writable = false;
+  while (grpc_chttp2_list_pop_writable_stream(t, &s)) {
+    bool sent_initial_metadata = s->sent_initial_metadata;
+    bool now_writing = false;
 
-    stream_writing->id = stream_global->id;
-    stream_writing->read_closed = stream_global->read_closed;
+    GRPC_CHTTP2_IF_TRACING(gpr_log(
+        GPR_DEBUG, "W:%p %s[%d] im-(sent,send)=(%d,%d) announce=%d", t,
+        t->is_client ? "CLIENT" : "SERVER", s->id, sent_initial_metadata,
+        s->send_initial_metadata != NULL, s->announce_window));
 
-    GRPC_CHTTP2_FLOW_MOVE_STREAM("write", transport_writing, stream_writing,
-                                 outgoing_window, stream_global,
-                                 outgoing_window);
-
-    if (!sent_initial_metadata && stream_global->send_initial_metadata) {
-      stream_writing->send_initial_metadata =
-          stream_global->send_initial_metadata;
-      stream_global->send_initial_metadata = NULL;
-      become_writable = true;
+    /* send initial metadata if it's available */
+    if (!sent_initial_metadata && s->send_initial_metadata) {
+      grpc_chttp2_encode_header(
+          &t->hpack_compressor, s->id, s->send_initial_metadata, 0,
+          t->settings[GRPC_ACKED_SETTINGS][GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],
+          &s->stats.outgoing, &t->outbuf);
+      s->send_initial_metadata = NULL;
+      s->sent_initial_metadata = true;
       sent_initial_metadata = true;
+      now_writing = true;
+    }
+    /* send any window updates */
+    if (s->announce_window > 0) {
+      uint32_t announce = s->announce_window;
+      gpr_slice_buffer_add(&t->outbuf,
+                           grpc_chttp2_window_update_create(
+                               s->id, s->announce_window, &s->stats.outgoing));
+      GRPC_CHTTP2_FLOW_DEBIT_STREAM("write", t, s, announce_window, announce);
     }
     if (sent_initial_metadata) {
-      if (stream_global->send_message != NULL) {
-        gpr_slice hdr = gpr_slice_malloc(5);
-        uint8_t *p = GPR_SLICE_START_PTR(hdr);
-        uint32_t len = stream_global->send_message->length;
-        GPR_ASSERT(stream_writing->send_message == NULL);
-        p[0] = (stream_global->send_message->flags &
-                GRPC_WRITE_INTERNAL_COMPRESS) != 0;
-        p[1] = (uint8_t)(len >> 24);
-        p[2] = (uint8_t)(len >> 16);
-        p[3] = (uint8_t)(len >> 8);
-        p[4] = (uint8_t)(len);
-        gpr_slice_buffer_add(&stream_writing->flow_controlled_buffer, hdr);
-        if (stream_global->send_message->length > 0) {
-          stream_writing->send_message = stream_global->send_message;
-        } else {
-          stream_writing->send_message = NULL;
-        }
-        stream_writing->stream_fetched = 0;
-        stream_global->send_message = NULL;
-      }
-      if ((stream_writing->send_message != NULL ||
-           stream_writing->flow_controlled_buffer.length > 0) &&
-          stream_writing->outgoing_window > 0) {
-        if (transport_writing->outgoing_window > 0) {
-          become_writable = true;
-        } else {
-          grpc_chttp2_list_add_stalled_by_transport(transport_writing,
-                                                    stream_writing);
+      /* send any body bytes, if allowed by flow control */
+      if (s->flow_controlled_buffer.length > 0) {
+        uint32_t max_outgoing =
+            (uint32_t)GPR_MIN(t->settings[GRPC_ACKED_SETTINGS]
+                                         [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],
+                              GPR_MIN(s->outgoing_window, t->outgoing_window));
+        if (max_outgoing > 0) {
+          uint32_t send_bytes =
+              (uint32_t)GPR_MIN(max_outgoing, s->flow_controlled_buffer.length);
+          bool is_last_data_frame =
+              s->fetching_send_message == NULL &&
+              send_bytes == s->flow_controlled_buffer.length;
+          bool is_last_frame =
+              is_last_data_frame && s->send_trailing_metadata != NULL &&
+              grpc_metadata_batch_is_empty(s->send_trailing_metadata);
+          grpc_chttp2_encode_data(s->id, &s->flow_controlled_buffer, send_bytes,
+                                  is_last_frame, &s->stats.outgoing,
+                                  &t->outbuf);
+          GRPC_CHTTP2_FLOW_DEBIT_STREAM("write", t, s, outgoing_window,
+                                        send_bytes);
+          GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("write", t, outgoing_window,
+                                           send_bytes);
+          if (is_last_frame) {
+            s->send_trailing_metadata = NULL;
+            s->sent_trailing_metadata = true;
+            if (!t->is_client && !s->read_closed) {
+              gpr_slice_buffer_add(&t->outbuf, grpc_chttp2_rst_stream_create(
+                                                   s->id, GRPC_CHTTP2_NO_ERROR,
+                                                   &s->stats.outgoing));
+            }
+          }
+          s->sending_bytes += send_bytes;
+          now_writing = true;
+          if (s->flow_controlled_buffer.length > 0) {
+            GRPC_CHTTP2_STREAM_REF(s, "chttp2_writing:fork");
+            grpc_chttp2_list_add_writable_stream(t, s);
+          }
+        } else if (t->outgoing_window == 0) {
+          grpc_chttp2_list_add_stalled_by_transport(t, s);
+          now_writing = true;
         }
       }
-      if (stream_global->send_trailing_metadata) {
-        stream_writing->send_trailing_metadata =
-            stream_global->send_trailing_metadata;
-        stream_global->send_trailing_metadata = NULL;
-        become_writable = true;
+      if (s->send_trailing_metadata != NULL &&
+          s->fetching_send_message == NULL &&
+          s->flow_controlled_buffer.length == 0) {
+        if (grpc_metadata_batch_is_empty(s->send_trailing_metadata)) {
+          grpc_chttp2_encode_data(s->id, &s->flow_controlled_buffer, 0, true,
+                                  &s->stats.outgoing, &t->outbuf);
+        } else {
+          grpc_chttp2_encode_header(
+              &t->hpack_compressor, s->id, s->send_trailing_metadata, true,
+              t->settings[GRPC_ACKED_SETTINGS]
+                         [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],
+              &s->stats.outgoing, &t->outbuf);
+        }
+        s->send_trailing_metadata = NULL;
+        s->sent_trailing_metadata = true;
+        if (!t->is_client && !s->read_closed) {
+          gpr_slice_buffer_add(
+              &t->outbuf, grpc_chttp2_rst_stream_create(
+                              s->id, GRPC_CHTTP2_NO_ERROR, &s->stats.outgoing));
+        }
+        now_writing = true;
       }
     }
 
-    if (!stream_global->read_closed &&
-        stream_global->unannounced_incoming_window_for_writing > 1024) {
-      GRPC_CHTTP2_FLOW_MOVE_STREAM("write", transport_global, stream_writing,
-                                   announce_window, stream_global,
-                                   unannounced_incoming_window_for_writing);
-      become_writable = true;
-    }
-
-    if (become_writable) {
-      grpc_chttp2_list_add_writing_stream(transport_writing, stream_writing);
+    if (now_writing) {
+      if (!grpc_chttp2_list_add_writing_stream(t, s)) {
+        /* already in writing list: drop ref */
+        GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2_writing:already_writing");
+      }
     } else {
-      GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2_writing");
+      GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2_writing:no_write");
     }
   }
 
   /* if the grpc_chttp2_transport is ready to send a window update, do so here
      also; 3/4 is a magic number that will likely get tuned soon */
-  if (transport_global->announce_incoming_window > 0) {
-    uint32_t announced = (uint32_t)GPR_MIN(
-        transport_global->announce_incoming_window, UINT32_MAX);
-    GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("write", transport_global,
-                                     announce_incoming_window, announced);
+  if (t->announce_incoming_window > 0) {
+    uint32_t announced =
+        (uint32_t)GPR_MIN(t->announce_incoming_window, UINT32_MAX);
+    GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("write", t, announce_incoming_window,
+                                     announced);
     grpc_transport_one_way_stats throwaway_stats;
-    gpr_slice_buffer_add(
-        &transport_writing->outbuf,
-        grpc_chttp2_window_update_create(0, announced, &throwaway_stats));
+    gpr_slice_buffer_add(&t->outbuf, grpc_chttp2_window_update_create(
+                                         0, announced, &throwaway_stats));
   }
 
-  GPR_TIMER_END("grpc_chttp2_unlocking_check_writes", 0);
+  GPR_TIMER_END("grpc_chttp2_begin_write", 0);
 
-  return transport_writing->outbuf.count > 0 ||
-         grpc_chttp2_list_have_writing_streams(transport_writing);
+  return t->outbuf.count > 0;
 }
 
-void grpc_chttp2_perform_writes(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing,
-    grpc_endpoint *endpoint) {
-  GPR_ASSERT(transport_writing->outbuf.count > 0 ||
-             grpc_chttp2_list_have_writing_streams(transport_writing));
+void grpc_chttp2_end_write(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
+                           grpc_error *error) {
+  GPR_TIMER_BEGIN("grpc_chttp2_end_write", 0);
+  grpc_chttp2_stream *s;
 
-  finalize_outbuf(exec_ctx, transport_writing);
-
-  GPR_ASSERT(endpoint);
-
-  if (transport_writing->outbuf.count > 0) {
-    grpc_endpoint_write(exec_ctx, endpoint, &transport_writing->outbuf,
-                        &transport_writing->done_cb);
-  } else {
-    grpc_exec_ctx_sched(exec_ctx, &transport_writing->done_cb, GRPC_ERROR_NONE,
-                        NULL);
-  }
-}
-
-static void finalize_outbuf(grpc_exec_ctx *exec_ctx,
-                            grpc_chttp2_transport_writing *transport_writing) {
-  grpc_chttp2_stream_writing *stream_writing;
-
-  GPR_TIMER_BEGIN("finalize_outbuf", 0);
-
-  bool is_first_data_frame = true;
-  while (
-      grpc_chttp2_list_pop_writing_stream(transport_writing, &stream_writing)) {
-    uint32_t max_outgoing =
-        (uint32_t)GPR_MIN(transport_writing->max_frame_size,
-                          GPR_MIN(stream_writing->outgoing_window,
-                                  transport_writing->outgoing_window));
-    /* send initial metadata if it's available */
-    if (stream_writing->send_initial_metadata != NULL) {
-      grpc_chttp2_encode_header(
-          &transport_writing->hpack_compressor, stream_writing->id,
-          stream_writing->send_initial_metadata, 0,
-          transport_writing->max_frame_size, &stream_writing->stats,
-          &transport_writing->outbuf);
-      stream_writing->send_initial_metadata = NULL;
-      stream_writing->sent_initial_metadata = 1;
-    }
-    /* send any window updates */
-    if (stream_writing->announce_window > 0 &&
-        stream_writing->send_initial_metadata == NULL) {
-      uint32_t announce = stream_writing->announce_window;
-      gpr_slice_buffer_add(
-          &transport_writing->outbuf,
-          grpc_chttp2_window_update_create(stream_writing->id,
-                                           stream_writing->announce_window,
-                                           &stream_writing->stats));
-      GRPC_CHTTP2_FLOW_DEBIT_STREAM("write", transport_writing, stream_writing,
-                                    announce_window, announce);
-      stream_writing->announce_window = 0;
-    }
-    /* fetch any body bytes */
-    while (!stream_writing->fetching && stream_writing->send_message &&
-           stream_writing->flow_controlled_buffer.length < max_outgoing &&
-           stream_writing->stream_fetched <
-               stream_writing->send_message->length) {
-      if (grpc_byte_stream_next(exec_ctx, stream_writing->send_message,
-                                &stream_writing->fetching_slice, max_outgoing,
-                                &stream_writing->finished_fetch)) {
-        stream_writing->stream_fetched +=
-            GPR_SLICE_LENGTH(stream_writing->fetching_slice);
-        if (stream_writing->stream_fetched ==
-            stream_writing->send_message->length) {
-          stream_writing->send_message = NULL;
-        }
-        gpr_slice_buffer_add(&stream_writing->flow_controlled_buffer,
-                             stream_writing->fetching_slice);
-      } else {
-        stream_writing->fetching = 1;
-      }
-    }
-    /* send any body bytes */
-    if (stream_writing->flow_controlled_buffer.length > 0) {
-      if (max_outgoing > 0) {
-        uint32_t send_bytes = (uint32_t)GPR_MIN(
-            max_outgoing, stream_writing->flow_controlled_buffer.length);
-        int is_last_data_frame =
-            stream_writing->send_message == NULL &&
-            send_bytes == stream_writing->flow_controlled_buffer.length;
-        int is_last_frame = is_last_data_frame &&
-                            stream_writing->send_trailing_metadata != NULL &&
-                            grpc_metadata_batch_is_empty(
-                                stream_writing->send_trailing_metadata);
-        grpc_chttp2_encode_data(
-            stream_writing->id, &stream_writing->flow_controlled_buffer,
-            send_bytes, is_last_frame, &stream_writing->stats,
-            &transport_writing->outbuf);
-        if (is_first_data_frame) {
-          /* TODO(dgq): this is a hack. It'll be fix in a future refactoring */
-          stream_writing->stats.data_bytes -= 5; /* discount grpc framing */
-          is_first_data_frame = false;
-        }
-        GRPC_CHTTP2_FLOW_DEBIT_STREAM("write", transport_writing,
-                                      stream_writing, outgoing_window,
-                                      send_bytes);
-        GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("write", transport_writing,
-                                         outgoing_window, send_bytes);
-        if (is_last_frame) {
-          stream_writing->send_trailing_metadata = NULL;
-          stream_writing->sent_trailing_metadata = 1;
-        }
-        if (is_last_data_frame) {
-          GPR_ASSERT(stream_writing->send_message == NULL);
-          stream_writing->sent_message = 1;
-        }
-      } else if (transport_writing->outgoing_window == 0) {
-        grpc_chttp2_list_add_writing_stalled_by_transport(transport_writing,
-                                                          stream_writing);
-        grpc_chttp2_list_add_written_stream(transport_writing, stream_writing);
-      }
-    }
-    /* send trailing metadata if it's available and we're ready for it */
-    if (stream_writing->send_message == NULL &&
-        stream_writing->flow_controlled_buffer.length == 0 &&
-        stream_writing->send_trailing_metadata != NULL) {
-      if (grpc_metadata_batch_is_empty(
-              stream_writing->send_trailing_metadata)) {
-        grpc_chttp2_encode_data(
-            stream_writing->id, &stream_writing->flow_controlled_buffer, 0, 1,
-            &stream_writing->stats, &transport_writing->outbuf);
-      } else {
-        grpc_chttp2_encode_header(
-            &transport_writing->hpack_compressor, stream_writing->id,
-            stream_writing->send_trailing_metadata, 1,
-            transport_writing->max_frame_size, &stream_writing->stats,
-            &transport_writing->outbuf);
-      }
-      if (!transport_writing->is_client && !stream_writing->read_closed) {
-        gpr_slice_buffer_add(&transport_writing->outbuf,
-                             grpc_chttp2_rst_stream_create(
-                                 stream_writing->id, GRPC_CHTTP2_NO_ERROR,
-                                 &stream_writing->stats));
-      }
-      stream_writing->send_trailing_metadata = NULL;
-      stream_writing->sent_trailing_metadata = 1;
-    }
-    /* if there's more to write, then loop, otherwise prepare to finish the
-     * write */
-    if ((stream_writing->flow_controlled_buffer.length > 0 ||
-         (stream_writing->send_message && !stream_writing->fetching)) &&
-        stream_writing->outgoing_window > 0) {
-      if (transport_writing->outgoing_window > 0) {
-        grpc_chttp2_list_add_writing_stream(transport_writing, stream_writing);
-      } else {
-        grpc_chttp2_list_add_writing_stalled_by_transport(transport_writing,
-                                                          stream_writing);
-        grpc_chttp2_list_add_written_stream(transport_writing, stream_writing);
-      }
-    } else {
-      grpc_chttp2_list_add_written_stream(transport_writing, stream_writing);
-    }
-  }
-
-  GPR_TIMER_END("finalize_outbuf", 0);
-}
-
-void grpc_chttp2_cleanup_writing(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global,
-    grpc_chttp2_transport_writing *transport_writing) {
-  GPR_TIMER_BEGIN("grpc_chttp2_cleanup_writing", 0);
-  grpc_chttp2_stream_writing *stream_writing;
-  grpc_chttp2_stream_global *stream_global;
-
-  if (grpc_chttp2_list_flush_writing_stalled_by_transport(exec_ctx,
-                                                          transport_writing)) {
-    grpc_chttp2_initiate_write(exec_ctx, transport_global, false,
-                               "resume_stalled_stream");
-  }
-
-  while (grpc_chttp2_list_pop_written_stream(
-      transport_global, transport_writing, &stream_global, &stream_writing)) {
-    if (stream_writing->sent_initial_metadata) {
+  while (grpc_chttp2_list_pop_writing_stream(t, &s)) {
+    if (s->sent_initial_metadata) {
       grpc_chttp2_complete_closure_step(
-          exec_ctx, transport_global, stream_global,
-          &stream_global->send_initial_metadata_finished, GRPC_ERROR_NONE);
+          exec_ctx, t, s, &s->send_initial_metadata_finished,
+          GRPC_ERROR_REF(error), "send_initial_metadata_finished");
     }
-    grpc_transport_move_one_way_stats(&stream_writing->stats,
-                                      &stream_global->stats.outgoing);
-    if (stream_writing->sent_message) {
-      GPR_ASSERT(stream_writing->send_message == NULL);
+    if (s->sending_bytes != 0) {
+      update_list(exec_ctx, t, s, (int64_t)s->sending_bytes,
+                  &s->on_write_finished_cbs, GRPC_ERROR_REF(error));
+      s->sending_bytes = 0;
+    }
+    if (s->sent_trailing_metadata) {
       grpc_chttp2_complete_closure_step(
-          exec_ctx, transport_global, stream_global,
-          &stream_global->send_message_finished, GRPC_ERROR_NONE);
-      stream_writing->sent_message = 0;
+          exec_ctx, t, s, &s->send_trailing_metadata_finished,
+          GRPC_ERROR_REF(error), "send_trailing_metadata_finished");
+      grpc_chttp2_mark_stream_closed(exec_ctx, t, s, !t->is_client, 1,
+                                     GRPC_ERROR_REF(error));
     }
-    if (stream_writing->sent_trailing_metadata) {
-      grpc_chttp2_complete_closure_step(
-          exec_ctx, transport_global, stream_global,
-          &stream_global->send_trailing_metadata_finished, GRPC_ERROR_NONE);
-    }
-    if (stream_writing->sent_trailing_metadata) {
-      grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global,
-                                     !transport_global->is_client, 1,
-                                     GRPC_ERROR_NONE);
-    }
-    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2_writing");
+    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2_writing:end");
   }
-  gpr_slice_buffer_reset_and_unref(&transport_writing->outbuf);
-  GPR_TIMER_END("grpc_chttp2_cleanup_writing", 0);
+  gpr_slice_buffer_reset_and_unref(&t->outbuf);
+  GRPC_ERROR_UNREF(error);
+  GPR_TIMER_END("grpc_chttp2_end_write", 0);
 }
diff --git a/src/core/lib/iomgr/closure.c b/src/core/lib/iomgr/closure.c
index 1ba0a5c..c6ddc76 100644
--- a/src/core/lib/iomgr/closure.c
+++ b/src/core/lib/iomgr/closure.c
@@ -35,6 +35,8 @@
 
 #include <grpc/support/alloc.h>
 
+#include "src/core/lib/profiling/timers.h"
+
 void grpc_closure_init(grpc_closure *closure, grpc_iomgr_cb_func cb,
                        void *cb_arg) {
   closure->cb = cb;
@@ -51,7 +53,7 @@
     GRPC_ERROR_UNREF(error);
     return;
   }
-  closure->error = error;
+  closure->error_data.error = error;
   closure->next_data.next = NULL;
   if (closure_list->head == NULL) {
     closure_list->head = closure;
@@ -64,8 +66,8 @@
 void grpc_closure_list_fail_all(grpc_closure_list *list,
                                 grpc_error *forced_failure) {
   for (grpc_closure *c = list->head; c != NULL; c = c->next_data.next) {
-    if (c->error == GRPC_ERROR_NONE) {
-      c->error = GRPC_ERROR_REF(forced_failure);
+    if (c->error_data.error == GRPC_ERROR_NONE) {
+      c->error_data.error = GRPC_ERROR_REF(forced_failure);
     }
   }
   GRPC_ERROR_UNREF(forced_failure);
@@ -110,3 +112,13 @@
   grpc_closure_init(&wc->wrapper, closure_wrapper, wc);
   return &wc->wrapper;
 }
+
+void grpc_closure_run(grpc_exec_ctx *exec_ctx, grpc_closure *c,
+                      grpc_error *error) {
+  GPR_TIMER_BEGIN("grpc_closure_run", 0);
+  if (c != NULL) {
+    c->cb(exec_ctx, c->cb_arg, error);
+  }
+  GRPC_ERROR_UNREF(error);
+  GPR_TIMER_END("grpc_closure_run", 0);
+}
diff --git a/src/core/lib/iomgr/closure.h b/src/core/lib/iomgr/closure.h
index c1a22b6..2b4b271 100644
--- a/src/core/lib/iomgr/closure.h
+++ b/src/core/lib/iomgr/closure.h
@@ -76,7 +76,10 @@
   void *cb_arg;
 
   /** Once queued, the result of the closure. Before then: scratch space */
-  grpc_error *error;
+  union {
+    grpc_error *error;
+    uintptr_t scratch;
+  } error_data;
 };
 
 /** Initializes \a closure with \a cb and \a cb_arg. */
@@ -106,4 +109,10 @@
 /** return whether \a list is empty. */
 bool grpc_closure_list_empty(grpc_closure_list list);
 
+/** Run a closure directly. Caller ensures that no locks are being held above.
+ *  Note that calling this at the end of a closure callback function itself is
+ *  by definition safe. */
+void grpc_closure_run(grpc_exec_ctx *exec_ctx, grpc_closure *closure,
+                      grpc_error *error);
+
 #endif /* GRPC_CORE_LIB_IOMGR_CLOSURE_H */
diff --git a/src/core/lib/iomgr/combiner.c b/src/core/lib/iomgr/combiner.c
index 831bdb4..60ee14e 100644
--- a/src/core/lib/iomgr/combiner.c
+++ b/src/core/lib/iomgr/combiner.c
@@ -50,25 +50,57 @@
     }                           \
   } while (0)
 
+#define STATE_UNORPHANED 1
+#define STATE_ELEM_COUNT_LOW_BIT 2
+
 struct grpc_combiner {
+  grpc_combiner *next_combiner_on_this_exec_ctx;
   grpc_workqueue *optional_workqueue;
   gpr_mpscq queue;
   // state is:
-  // lower bit - zero if orphaned
-  // other bits - number of items queued on the lock
+  // lower bit - zero if orphaned (STATE_UNORPHANED)
+  // other bits - number of items queued on the lock (STATE_ELEM_COUNT_LOW_BIT)
   gpr_atm state;
-  bool take_async_break_before_final_list;
+  // number of elements in the list that are covered by a poller: if >0, we can
+  // offload safely
+  gpr_atm elements_covered_by_poller;
+  bool time_to_execute_final_list;
+  bool final_list_covered_by_poller;
   grpc_closure_list final_list;
-  grpc_closure continue_finishing;
+  grpc_closure offload;
 };
 
+static void offload(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error);
+
+typedef struct {
+  grpc_error *error;
+  bool covered_by_poller;
+} error_data;
+
+static uintptr_t pack_error_data(error_data d) {
+  return ((uintptr_t)d.error) | (d.covered_by_poller ? 1 : 0);
+}
+
+static error_data unpack_error_data(uintptr_t p) {
+  return (error_data){(grpc_error *)(p & ~(uintptr_t)1), p & 1};
+}
+
+static bool is_covered_by_poller(grpc_combiner *lock) {
+  return lock->final_list_covered_by_poller ||
+         gpr_atm_acq_load(&lock->elements_covered_by_poller) > 0;
+}
+
 grpc_combiner *grpc_combiner_create(grpc_workqueue *optional_workqueue) {
   grpc_combiner *lock = gpr_malloc(sizeof(*lock));
+  lock->next_combiner_on_this_exec_ctx = NULL;
+  lock->time_to_execute_final_list = false;
   lock->optional_workqueue = optional_workqueue;
-  gpr_atm_no_barrier_store(&lock->state, 1);
+  lock->final_list_covered_by_poller = false;
+  gpr_atm_no_barrier_store(&lock->state, STATE_UNORPHANED);
+  gpr_atm_no_barrier_store(&lock->elements_covered_by_poller, 0);
   gpr_mpscq_init(&lock->queue);
-  lock->take_async_break_before_final_list = false;
   grpc_closure_list_init(&lock->final_list);
+  grpc_closure_init(&lock->offload, offload, lock);
   GRPC_COMBINER_TRACE(gpr_log(GPR_DEBUG, "C:%p create", lock));
   return lock;
 }
@@ -82,7 +114,7 @@
 }
 
 void grpc_combiner_destroy(grpc_exec_ctx *exec_ctx, grpc_combiner *lock) {
-  gpr_atm old_state = gpr_atm_full_fetch_add(&lock->state, -1);
+  gpr_atm old_state = gpr_atm_full_fetch_add(&lock->state, -STATE_UNORPHANED);
   GRPC_COMBINER_TRACE(gpr_log(
       GPR_DEBUG, "C:%p really_destroy old_state=%" PRIdPTR, lock, old_state));
   if (old_state == 1) {
@@ -90,172 +122,188 @@
   }
 }
 
-static bool maybe_finish_one(grpc_exec_ctx *exec_ctx, grpc_combiner *lock);
-static void finish(grpc_exec_ctx *exec_ctx, grpc_combiner *lock);
-
-static void continue_finishing_mainline(grpc_exec_ctx *exec_ctx, void *arg,
-                                        grpc_error *error) {
-  GPR_TIMER_BEGIN("combiner.continue_executing_mainline", 0);
-  grpc_combiner *lock = arg;
-  GRPC_COMBINER_TRACE(
-      gpr_log(GPR_DEBUG, "C:%p continue_finishing_mainline", lock));
-  GPR_ASSERT(exec_ctx->active_combiner == NULL);
-  exec_ctx->active_combiner = lock;
-  if (maybe_finish_one(exec_ctx, lock)) finish(exec_ctx, lock);
-  GPR_ASSERT(exec_ctx->active_combiner == lock);
-  exec_ctx->active_combiner = NULL;
-  GPR_TIMER_END("combiner.continue_executing_mainline", 0);
-}
-
-static void execute_final(grpc_exec_ctx *exec_ctx, grpc_combiner *lock) {
-  GPR_TIMER_BEGIN("combiner.execute_final", 0);
-  grpc_closure *c = lock->final_list.head;
-  GPR_ASSERT(c != NULL);
-  grpc_closure_list_init(&lock->final_list);
-  lock->take_async_break_before_final_list = false;
-  int loops = 0;
-  while (c != NULL) {
-    GRPC_COMBINER_TRACE(
-        gpr_log(GPR_DEBUG, "C:%p execute_final[%d] c=%p", lock, loops, c));
-    grpc_closure *next = c->next_data.next;
-    grpc_error *error = c->error;
-    c->cb(exec_ctx, c->cb_arg, error);
-    GRPC_ERROR_UNREF(error);
-    c = next;
-    loops++;
-  }
-  GPR_TIMER_END("combiner.execute_final", 0);
-}
-
-static void continue_executing_final(grpc_exec_ctx *exec_ctx, void *arg,
-                                     grpc_error *error) {
-  GPR_TIMER_BEGIN("combiner.continue_executing_final", 0);
-  grpc_combiner *lock = arg;
-  GRPC_COMBINER_TRACE(
-      gpr_log(GPR_DEBUG, "C:%p continue_executing_final", lock));
-  GPR_ASSERT(exec_ctx->active_combiner == NULL);
-  exec_ctx->active_combiner = lock;
-  // quick peek to see if new things have turned up on the queue: if so, go back
-  // to executing them before the final list
-  if ((gpr_atm_acq_load(&lock->state) >> 1) > 1) {
-    if (maybe_finish_one(exec_ctx, lock)) finish(exec_ctx, lock);
+static void push_last_on_exec_ctx(grpc_exec_ctx *exec_ctx,
+                                  grpc_combiner *lock) {
+  lock->next_combiner_on_this_exec_ctx = NULL;
+  if (exec_ctx->active_combiner == NULL) {
+    exec_ctx->active_combiner = exec_ctx->last_combiner = lock;
   } else {
-    execute_final(exec_ctx, lock);
-    finish(exec_ctx, lock);
-  }
-  GPR_ASSERT(exec_ctx->active_combiner == lock);
-  exec_ctx->active_combiner = NULL;
-  GPR_TIMER_END("combiner.continue_executing_final", 0);
-}
-
-static bool start_execute_final(grpc_exec_ctx *exec_ctx, grpc_combiner *lock) {
-  GPR_TIMER_BEGIN("combiner.start_execute_final", 0);
-  GPR_ASSERT(exec_ctx->active_combiner == lock);
-  GRPC_COMBINER_TRACE(
-      gpr_log(GPR_DEBUG,
-              "C:%p start_execute_final take_async_break_before_final_list=%d",
-              lock, lock->take_async_break_before_final_list));
-  if (lock->take_async_break_before_final_list) {
-    grpc_closure_init(&lock->continue_finishing, continue_executing_final,
-                      lock);
-    grpc_exec_ctx_sched(exec_ctx, &lock->continue_finishing, GRPC_ERROR_NONE,
-                        GRPC_WORKQUEUE_REF(lock->optional_workqueue, "sched"));
-    GPR_TIMER_END("combiner.start_execute_final", 0);
-    return false;
-  } else {
-    execute_final(exec_ctx, lock);
-    GPR_TIMER_END("combiner.start_execute_final", 0);
-    return true;
+    exec_ctx->last_combiner->next_combiner_on_this_exec_ctx = lock;
+    exec_ctx->last_combiner = lock;
   }
 }
 
-static bool maybe_finish_one(grpc_exec_ctx *exec_ctx, grpc_combiner *lock) {
-  GPR_TIMER_BEGIN("combiner.maybe_finish_one", 0);
-  gpr_mpscq_node *n = gpr_mpscq_pop(&lock->queue);
-  GRPC_COMBINER_TRACE(
-      gpr_log(GPR_DEBUG, "C:%p maybe_finish_one n=%p", lock, n));
-  GPR_ASSERT(exec_ctx->active_combiner == lock);
-  if (n == NULL) {
-    // Queue is in an transiently inconsistent state: a new item is being queued
-    // but is not visible to this thread yet.
-    // Use this as a cue that we should go off and do something else for a while
-    // (and come back later)
-    grpc_closure_init(&lock->continue_finishing, continue_finishing_mainline,
-                      lock);
-    grpc_exec_ctx_sched(exec_ctx, &lock->continue_finishing, GRPC_ERROR_NONE,
-                        GRPC_WORKQUEUE_REF(lock->optional_workqueue, "sched"));
-    GPR_TIMER_END("combiner.maybe_finish_one", 0);
-    return false;
+static void push_first_on_exec_ctx(grpc_exec_ctx *exec_ctx,
+                                   grpc_combiner *lock) {
+  lock->next_combiner_on_this_exec_ctx = exec_ctx->active_combiner;
+  exec_ctx->active_combiner = lock;
+  if (lock->next_combiner_on_this_exec_ctx == NULL) {
+    exec_ctx->last_combiner = lock;
   }
-  grpc_closure *cl = (grpc_closure *)n;
-  grpc_error *error = cl->error;
-  cl->cb(exec_ctx, cl->cb_arg, error);
-  GRPC_ERROR_UNREF(error);
-  GPR_TIMER_END("combiner.maybe_finish_one", 0);
-  return true;
-}
-
-static void finish(grpc_exec_ctx *exec_ctx, grpc_combiner *lock) {
-  bool (*executor)(grpc_exec_ctx * exec_ctx, grpc_combiner * lock);
-  GPR_TIMER_BEGIN("combiner.finish", 0);
-  int loops = 0;
-  do {
-    executor = maybe_finish_one;
-    gpr_atm old_state = gpr_atm_full_fetch_add(&lock->state, -2);
-    GRPC_COMBINER_TRACE(gpr_log(GPR_DEBUG,
-                                "C:%p finish[%d] old_state=%" PRIdPTR, lock,
-                                loops, old_state));
-    switch (old_state) {
-      default:
-        // we have multiple queued work items: just continue executing them
-        break;
-      case 5:  // we're down to one queued item: if it's the final list we
-      case 4:  // should do that
-        if (!grpc_closure_list_empty(lock->final_list)) {
-          executor = start_execute_final;
-        }
-        break;
-      case 3:  // had one count, one unorphaned --> unlocked unorphaned
-        GPR_TIMER_END("combiner.finish", 0);
-        return;
-      case 2:  // and one count, one orphaned --> unlocked and orphaned
-        really_destroy(exec_ctx, lock);
-        GPR_TIMER_END("combiner.finish", 0);
-        return;
-      case 1:
-      case 0:
-        // these values are illegal - representing an already unlocked or
-        // deleted lock
-        GPR_UNREACHABLE_CODE(return );
-    }
-    loops++;
-  } while (executor(exec_ctx, lock));
-  GPR_TIMER_END("combiner.finish", 0);
 }
 
 void grpc_combiner_execute(grpc_exec_ctx *exec_ctx, grpc_combiner *lock,
-                           grpc_closure *cl, grpc_error *error) {
-  GRPC_COMBINER_TRACE(
-      gpr_log(GPR_DEBUG, "C:%p grpc_combiner_execute c=%p", lock, cl));
+                           grpc_closure *cl, grpc_error *error,
+                           bool covered_by_poller) {
   GPR_TIMER_BEGIN("combiner.execute", 0);
-  gpr_atm last = gpr_atm_full_fetch_add(&lock->state, 2);
-  GPR_ASSERT(last & 1);  // ensure lock has not been destroyed
+  gpr_atm last = gpr_atm_full_fetch_add(&lock->state, STATE_ELEM_COUNT_LOW_BIT);
+  GRPC_COMBINER_TRACE(gpr_log(
+      GPR_DEBUG, "C:%p grpc_combiner_execute c=%p cov=%d last=%" PRIdPTR, lock,
+      cl, covered_by_poller, last));
+  GPR_ASSERT(last & STATE_UNORPHANED);  // ensure lock has not been destroyed
+  cl->error_data.scratch =
+      pack_error_data((error_data){error, covered_by_poller});
+  if (covered_by_poller) {
+    gpr_atm_no_barrier_fetch_add(&lock->elements_covered_by_poller, 1);
+  }
+  gpr_mpscq_push(&lock->queue, &cl->next_data.atm_next);
   if (last == 1) {
-    exec_ctx->active_combiner = lock;
-    GPR_TIMER_BEGIN("combiner.execute_first_cb", 0);
-    cl->cb(exec_ctx, cl->cb_arg, error);
-    GPR_TIMER_END("combiner.execute_first_cb", 0);
-    GRPC_ERROR_UNREF(error);
-    finish(exec_ctx, lock);
-    GPR_ASSERT(exec_ctx->active_combiner == lock);
-    exec_ctx->active_combiner = NULL;
-  } else {
-    cl->error = error;
-    gpr_mpscq_push(&lock->queue, &cl->next_data.atm_next);
+    // first element on this list: add it to the list of combiner locks
+    // executing within this exec_ctx
+    push_last_on_exec_ctx(exec_ctx, lock);
   }
   GPR_TIMER_END("combiner.execute", 0);
 }
 
+static void move_next(grpc_exec_ctx *exec_ctx) {
+  exec_ctx->active_combiner =
+      exec_ctx->active_combiner->next_combiner_on_this_exec_ctx;
+  if (exec_ctx->active_combiner == NULL) {
+    exec_ctx->last_combiner = NULL;
+  }
+}
+
+static void offload(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
+  grpc_combiner *lock = arg;
+  push_last_on_exec_ctx(exec_ctx, lock);
+}
+
+static void queue_offload(grpc_exec_ctx *exec_ctx, grpc_combiner *lock) {
+  move_next(exec_ctx);
+  GRPC_COMBINER_TRACE(gpr_log(GPR_DEBUG, "C:%p queue_offload --> %p", lock,
+                              lock->optional_workqueue));
+  grpc_workqueue_enqueue(exec_ctx, lock->optional_workqueue, &lock->offload,
+                         GRPC_ERROR_NONE);
+}
+
+bool grpc_combiner_continue_exec_ctx(grpc_exec_ctx *exec_ctx) {
+  GPR_TIMER_BEGIN("combiner.continue_exec_ctx", 0);
+  grpc_combiner *lock = exec_ctx->active_combiner;
+  if (lock == NULL) {
+    GPR_TIMER_END("combiner.continue_exec_ctx", 0);
+    return false;
+  }
+
+  GRPC_COMBINER_TRACE(
+      gpr_log(GPR_DEBUG,
+              "C:%p grpc_combiner_continue_exec_ctx workqueue=%p "
+              "is_covered_by_poller=%d exec_ctx_ready_to_finish=%d "
+              "time_to_execute_final_list=%d",
+              lock, lock->optional_workqueue, is_covered_by_poller(lock),
+              grpc_exec_ctx_ready_to_finish(exec_ctx),
+              lock->time_to_execute_final_list));
+
+  if (lock->optional_workqueue != NULL && is_covered_by_poller(lock) &&
+      grpc_exec_ctx_ready_to_finish(exec_ctx)) {
+    GPR_TIMER_MARK("offload_from_finished_exec_ctx", 0);
+    // this execution context wants to move on, and we have a workqueue (and
+    // so can help the execution context out): schedule remaining work to be
+    // picked up on the workqueue
+    queue_offload(exec_ctx, lock);
+    GPR_TIMER_END("combiner.continue_exec_ctx", 0);
+    return true;
+  }
+
+  if (!lock->time_to_execute_final_list ||
+      // peek to see if something new has shown up, and execute that with
+      // priority
+      (gpr_atm_acq_load(&lock->state) >> 1) > 1) {
+    gpr_mpscq_node *n = gpr_mpscq_pop(&lock->queue);
+    GRPC_COMBINER_TRACE(
+        gpr_log(GPR_DEBUG, "C:%p maybe_finish_one n=%p", lock, n));
+    if (n == NULL) {
+      // queue is in an inconsistent state: use this as a cue that we should
+      // go off and do something else for a while (and come back later)
+      GPR_TIMER_MARK("delay_busy", 0);
+      if (lock->optional_workqueue != NULL && is_covered_by_poller(lock)) {
+        queue_offload(exec_ctx, lock);
+      }
+      GPR_TIMER_END("combiner.continue_exec_ctx", 0);
+      return true;
+    }
+    GPR_TIMER_BEGIN("combiner.exec1", 0);
+    grpc_closure *cl = (grpc_closure *)n;
+    error_data err = unpack_error_data(cl->error_data.scratch);
+    cl->cb(exec_ctx, cl->cb_arg, err.error);
+    if (err.covered_by_poller) {
+      gpr_atm_no_barrier_fetch_add(&lock->elements_covered_by_poller, -1);
+    }
+    GRPC_ERROR_UNREF(err.error);
+    GPR_TIMER_END("combiner.exec1", 0);
+  } else {
+    grpc_closure *c = lock->final_list.head;
+    GPR_ASSERT(c != NULL);
+    grpc_closure_list_init(&lock->final_list);
+    lock->final_list_covered_by_poller = false;
+    int loops = 0;
+    while (c != NULL) {
+      GPR_TIMER_BEGIN("combiner.exec_1final", 0);
+      GRPC_COMBINER_TRACE(
+          gpr_log(GPR_DEBUG, "C:%p execute_final[%d] c=%p", lock, loops, c));
+      grpc_closure *next = c->next_data.next;
+      grpc_error *error = c->error_data.error;
+      c->cb(exec_ctx, c->cb_arg, error);
+      GRPC_ERROR_UNREF(error);
+      c = next;
+      GPR_TIMER_END("combiner.exec_1final", 0);
+    }
+  }
+
+  GPR_TIMER_MARK("unref", 0);
+  move_next(exec_ctx);
+  lock->time_to_execute_final_list = false;
+  gpr_atm old_state =
+      gpr_atm_full_fetch_add(&lock->state, -STATE_ELEM_COUNT_LOW_BIT);
+  GRPC_COMBINER_TRACE(
+      gpr_log(GPR_DEBUG, "C:%p finish old_state=%" PRIdPTR, lock, old_state));
+// Define a macro to ease readability of the following switch statement.
+#define OLD_STATE_WAS(orphaned, elem_count) \
+  (((orphaned) ? 0 : STATE_UNORPHANED) |    \
+   ((elem_count)*STATE_ELEM_COUNT_LOW_BIT))
+  // Depending on what the previous state was, we need to perform different
+  // actions.
+  switch (old_state) {
+    default:
+      // we have multiple queued work items: just continue executing them
+      break;
+    case OLD_STATE_WAS(false, 2):
+    case OLD_STATE_WAS(true, 2):
+      // we're down to one queued item: if it's the final list we should do that
+      if (!grpc_closure_list_empty(lock->final_list)) {
+        lock->time_to_execute_final_list = true;
+      }
+      break;
+    case OLD_STATE_WAS(false, 1):
+      // had one count, one unorphaned --> unlocked unorphaned
+      GPR_TIMER_END("combiner.continue_exec_ctx", 0);
+      return true;
+    case OLD_STATE_WAS(true, 1):
+      // and one count, one orphaned --> unlocked and orphaned
+      really_destroy(exec_ctx, lock);
+      GPR_TIMER_END("combiner.continue_exec_ctx", 0);
+      return true;
+    case OLD_STATE_WAS(false, 0):
+    case OLD_STATE_WAS(true, 0):
+      // these values are illegal - representing an already unlocked or
+      // deleted lock
+      GPR_TIMER_END("combiner.continue_exec_ctx", 0);
+      GPR_UNREACHABLE_CODE(return true);
+  }
+  push_first_on_exec_ctx(exec_ctx, lock);
+  GPR_TIMER_END("combiner.continue_exec_ctx", 0);
+  return true;
+}
+
 static void enqueue_finally(grpc_exec_ctx *exec_ctx, void *closure,
                             grpc_error *error) {
   grpc_combiner_execute_finally(exec_ctx, exec_ctx->active_combiner, closure,
@@ -264,30 +312,26 @@
 
 void grpc_combiner_execute_finally(grpc_exec_ctx *exec_ctx, grpc_combiner *lock,
                                    grpc_closure *closure, grpc_error *error,
-                                   bool force_async_break) {
+                                   bool covered_by_poller) {
   GRPC_COMBINER_TRACE(gpr_log(
-      GPR_DEBUG,
-      "C:%p grpc_combiner_execute_finally c=%p force_async_break=%d; ac=%p",
-      lock, closure, force_async_break, exec_ctx->active_combiner));
+      GPR_DEBUG, "C:%p grpc_combiner_execute_finally c=%p; ac=%p; cov=%d", lock,
+      closure, exec_ctx->active_combiner, covered_by_poller));
   GPR_TIMER_BEGIN("combiner.execute_finally", 0);
   if (exec_ctx->active_combiner != lock) {
     GPR_TIMER_MARK("slowpath", 0);
     grpc_combiner_execute(exec_ctx, lock,
-                          grpc_closure_create(enqueue_finally, closure), error);
+                          grpc_closure_create(enqueue_finally, closure), error,
+                          false);
     GPR_TIMER_END("combiner.execute_finally", 0);
     return;
   }
 
-  if (force_async_break) {
-    lock->take_async_break_before_final_list = true;
-  }
   if (grpc_closure_list_empty(lock->final_list)) {
-    gpr_atm_full_fetch_add(&lock->state, 2);
+    gpr_atm_full_fetch_add(&lock->state, STATE_ELEM_COUNT_LOW_BIT);
+  }
+  if (covered_by_poller) {
+    lock->final_list_covered_by_poller = true;
   }
   grpc_closure_list_append(&lock->final_list, closure, error);
   GPR_TIMER_END("combiner.execute_finally", 0);
 }
-
-void grpc_combiner_force_async_finally(grpc_combiner *lock) {
-  lock->take_async_break_before_final_list = true;
-}
diff --git a/src/core/lib/iomgr/combiner.h b/src/core/lib/iomgr/combiner.h
index 1409db2..d04eeed 100644
--- a/src/core/lib/iomgr/combiner.h
+++ b/src/core/lib/iomgr/combiner.h
@@ -52,19 +52,14 @@
 void grpc_combiner_destroy(grpc_exec_ctx *exec_ctx, grpc_combiner *lock);
 // Execute \a action within the lock.
 void grpc_combiner_execute(grpc_exec_ctx *exec_ctx, grpc_combiner *lock,
-                           grpc_closure *closure, grpc_error *error);
+                           grpc_closure *closure, grpc_error *error,
+                           bool covered_by_poller);
 // Execute \a action within the lock just prior to unlocking.
-// if \a hint_async_break is true, the combiner tries to hand execution to
-// another thread before finishing the primary queue of combined closures and
-// executing the finally list.
-// Deprecation warning: \a hint_async_break will be removed in a future version
-// Takes a very slow and round-about path if not called from a
-// grpc_combiner_execute closure.
 void grpc_combiner_execute_finally(grpc_exec_ctx *exec_ctx, grpc_combiner *lock,
                                    grpc_closure *closure, grpc_error *error,
-                                   bool hint_async_break);
-// Deprecated: force the finally list execution onto another thread
-void grpc_combiner_force_async_finally(grpc_combiner *lock);
+                                   bool covered_by_poller);
+
+bool grpc_combiner_continue_exec_ctx(grpc_exec_ctx *exec_ctx);
 
 extern int grpc_combiner_trace;
 
diff --git a/src/core/lib/iomgr/error.c b/src/core/lib/iomgr/error.c
index 31c8026..f6bb3a0 100644
--- a/src/core/lib/iomgr/error.c
+++ b/src/core/lib/iomgr/error.c
@@ -120,6 +120,8 @@
       return "http_status";
     case GRPC_ERROR_INT_LIMIT:
       return "limit";
+    case GRPC_ERROR_INT_OCCURRED_DURING_WRITE:
+      return "occurred_during_write";
   }
   GPR_UNREACHABLE_CODE(return "unknown");
 }
@@ -144,6 +146,8 @@
       return "tsi_error";
     case GRPC_ERROR_STR_FILENAME:
       return "filename";
+    case GRPC_ERROR_STR_QUEUED_BUFFERS:
+      return "queued_buffers";
   }
   GPR_UNREACHABLE_CODE(return "unknown");
 }
@@ -265,7 +269,7 @@
   } else {
     out = gpr_malloc(sizeof(*out));
 #ifdef GRPC_ERROR_REFCOUNT_DEBUG
-    gpr_log(GPR_DEBUG, "%p create copying", out);
+    gpr_log(GPR_DEBUG, "%p create copying %p", out, in);
 #endif
     out->ints = gpr_avl_ref(in->ints);
     out->strs = gpr_avl_ref(in->strs);
@@ -523,21 +527,25 @@
   return out;
 }
 
-static void add_errs(gpr_avl_node *n, char **s, size_t *sz, size_t *cap) {
+static void add_errs(gpr_avl_node *n, char **s, size_t *sz, size_t *cap,
+                     bool *first) {
   if (n == NULL) return;
-  add_errs(n->left, s, sz, cap);
+  add_errs(n->left, s, sz, cap, first);
+  if (!*first) append_chr(',', s, sz, cap);
+  *first = false;
   const char *e = grpc_error_string(n->value);
   append_str(e, s, sz, cap);
   grpc_error_free_string(e);
-  add_errs(n->right, s, sz, cap);
+  add_errs(n->right, s, sz, cap, first);
 }
 
 static char *errs_string(grpc_error *err) {
   char *s = NULL;
   size_t sz = 0;
   size_t cap = 0;
+  bool first = true;
   append_chr('[', &s, &sz, &cap);
-  add_errs(err->errs.root, &s, &sz, &cap);
+  add_errs(err->errs.root, &s, &sz, &cap, &first);
   append_chr(']', &s, &sz, &cap);
   append_chr(0, &s, &sz, &cap);
   return s;
diff --git a/src/core/lib/iomgr/error.h b/src/core/lib/iomgr/error.h
index 00ace8a..f3f3b80 100644
--- a/src/core/lib/iomgr/error.h
+++ b/src/core/lib/iomgr/error.h
@@ -100,6 +100,8 @@
   GRPC_ERROR_INT_HTTP_STATUS,
   /// context sensitive limit associated with the error
   GRPC_ERROR_INT_LIMIT,
+  /// chttp2: did the error occur while a write was in progress
+  GRPC_ERROR_INT_OCCURRED_DURING_WRITE,
 } grpc_error_ints;
 
 typedef enum {
@@ -121,6 +123,8 @@
   GRPC_ERROR_STR_TSI_ERROR,
   /// filename that we were trying to read/write when this error occurred
   GRPC_ERROR_STR_FILENAME,
+  /// which data was queued for writing when the error occurred
+  GRPC_ERROR_STR_QUEUED_BUFFERS
 } grpc_error_strs;
 
 typedef enum {
@@ -128,9 +132,13 @@
   GRPC_ERROR_TIME_CREATED,
 } grpc_error_times;
 
+/// The following "special" errors can be propagated without allocating memory.
+/// They are always even so that other code (particularly combiner locks) can
+/// safely use the lower bit for themselves.
+
 #define GRPC_ERROR_NONE ((grpc_error *)NULL)
-#define GRPC_ERROR_OOM ((grpc_error *)1)
-#define GRPC_ERROR_CANCELLED ((grpc_error *)2)
+#define GRPC_ERROR_OOM ((grpc_error *)2)
+#define GRPC_ERROR_CANCELLED ((grpc_error *)4)
 
 const char *grpc_error_string(grpc_error *error);
 void grpc_error_free_string(const char *str);
diff --git a/src/core/lib/iomgr/ev_epoll_linux.c b/src/core/lib/iomgr/ev_epoll_linux.c
index ab77ebc..e5909d9 100644
--- a/src/core/lib/iomgr/ev_epoll_linux.c
+++ b/src/core/lib/iomgr/ev_epoll_linux.c
@@ -152,20 +152,20 @@
  * Polling island Declarations
  */
 
-//#define GRPC_PI_REF_COUNT_DEBUG
-#ifdef GRPC_PI_REF_COUNT_DEBUG
+#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
 
 #define PI_ADD_REF(p, r) pi_add_ref_dbg((p), (r), __FILE__, __LINE__)
 #define PI_UNREF(exec_ctx, p, r) \
   pi_unref_dbg((exec_ctx), (p), (r), __FILE__, __LINE__)
 
-#else /* defined(GRPC_PI_REF_COUNT_DEBUG) */
+#else /* defined(GRPC_WORKQUEUE_REFCOUNT_DEBUG) */
 
 #define PI_ADD_REF(p, r) pi_add_ref((p))
 #define PI_UNREF(exec_ctx, p, r) pi_unref((exec_ctx), (p))
 
 #endif /* !defined(GPRC_PI_REF_COUNT_DEBUG) */
 
+/* This is also used as grpc_workqueue (by directly casing it) */
 typedef struct polling_island {
   gpr_mu mu;
   /* Ref count. Use PI_ADD_REF() and PI_UNREF() macros to increment/decrement
@@ -185,8 +185,17 @@
    * (except mu and ref_count) are invalid and must be ignored. */
   gpr_atm merged_to;
 
-  /* The workqueue associated with this polling island */
-  grpc_workqueue *workqueue;
+  /* Number of threads currently polling on this island */
+  gpr_atm poller_count;
+  /* Mutex guarding the read end of the workqueue (must be held to pop from
+   * workqueue_items) */
+  gpr_mu workqueue_read_mu;
+  /* Queue of closures to be executed */
+  gpr_mpscq workqueue_items;
+  /* Count of items in workqueue_items */
+  gpr_atm workqueue_item_count;
+  /* Wakeup fd used to wake pollers to check the contents of workqueue_items */
+  grpc_wakeup_fd workqueue_wakeup_fd;
 
   /* The fd of the underlying epoll set */
   int epoll_fd;
@@ -275,6 +284,10 @@
    threads that woke up MUST NOT call grpc_wakeup_fd_consume_wakeup() */
 static grpc_wakeup_fd polling_island_wakeup_fd;
 
+/* The polling island being polled right now.
+   See comments in workqueue_maybe_wakeup for why this is tracked. */
+static __thread polling_island *g_current_thread_polling_island;
+
 /* Forward declaration */
 static void polling_island_delete(grpc_exec_ctx *exec_ctx, polling_island *pi);
 
@@ -289,12 +302,12 @@
 gpr_atm g_epoll_sync;
 #endif /* defined(GRPC_TSAN) */
 
-#ifdef GRPC_PI_REF_COUNT_DEBUG
 static void pi_add_ref(polling_island *pi);
 static void pi_unref(grpc_exec_ctx *exec_ctx, polling_island *pi);
 
-static void pi_add_ref_dbg(polling_island *pi, char *reason, char *file,
-                           int line) {
+#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
+static void pi_add_ref_dbg(polling_island *pi, const char *reason,
+                           const char *file, int line) {
   long old_cnt = gpr_atm_acq_load(&pi->ref_count);
   pi_add_ref(pi);
   gpr_log(GPR_DEBUG, "Add ref pi: %p, old: %ld -> new:%ld (%s) - (%s, %d)",
@@ -302,12 +315,42 @@
 }
 
 static void pi_unref_dbg(grpc_exec_ctx *exec_ctx, polling_island *pi,
-                         char *reason, char *file, int line) {
+                         const char *reason, const char *file, int line) {
   long old_cnt = gpr_atm_acq_load(&pi->ref_count);
   pi_unref(exec_ctx, pi);
   gpr_log(GPR_DEBUG, "Unref pi: %p, old:%ld -> new:%ld (%s) - (%s, %d)",
           (void *)pi, old_cnt, (old_cnt - 1), reason, file, line);
 }
+
+static grpc_workqueue *workqueue_ref(grpc_workqueue *workqueue,
+                                     const char *file, int line,
+                                     const char *reason) {
+  if (workqueue != NULL) {
+    pi_add_ref_dbg((polling_island *)workqueue, reason, file, line);
+  }
+  return workqueue;
+}
+
+static void workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
+                            const char *file, int line, const char *reason) {
+  if (workqueue != NULL) {
+    pi_unref_dbg(exec_ctx, (polling_island *)workqueue, reason, file, line);
+  }
+}
+#else
+static grpc_workqueue *workqueue_ref(grpc_workqueue *workqueue) {
+  if (workqueue != NULL) {
+    pi_add_ref((polling_island *)workqueue);
+  }
+  return workqueue;
+}
+
+static void workqueue_unref(grpc_exec_ctx *exec_ctx,
+                            grpc_workqueue *workqueue) {
+  if (workqueue != NULL) {
+    pi_unref(exec_ctx, (polling_island *)workqueue);
+  }
+}
 #endif
 
 static void pi_add_ref(polling_island *pi) {
@@ -315,10 +358,7 @@
 }
 
 static void pi_unref(grpc_exec_ctx *exec_ctx, polling_island *pi) {
-  /* If ref count went to one, we're back to just the workqueue owning a ref.
-     Unref the workqueue to break the loop.
-
-     If ref count went to zero, delete the polling island.
+  /* If ref count went to zero, delete the polling island.
      Note that this deletion not be done under a lock. Once the ref count goes
      to zero, we are guaranteed that no one else holds a reference to the
      polling island (and that there is no racing pi_add_ref() call either).
@@ -326,20 +366,12 @@
      Also, if we are deleting the polling island and the merged_to field is
      non-empty, we should remove a ref to the merged_to polling island
    */
-  switch (gpr_atm_full_fetch_add(&pi->ref_count, -1)) {
-    case 2: /* last external ref: the only one now owned is by the workqueue */
-      GRPC_WORKQUEUE_UNREF(exec_ctx, pi->workqueue, "polling_island");
-      break;
-    case 1: {
-      polling_island *next = (polling_island *)gpr_atm_acq_load(&pi->merged_to);
-      polling_island_delete(exec_ctx, pi);
-      if (next != NULL) {
-        PI_UNREF(exec_ctx, next, "pi_delete"); /* Recursive call */
-      }
-      break;
+  if (1 == gpr_atm_full_fetch_add(&pi->ref_count, -1)) {
+    polling_island *next = (polling_island *)gpr_atm_acq_load(&pi->merged_to);
+    polling_island_delete(exec_ctx, pi);
+    if (next != NULL) {
+      PI_UNREF(exec_ctx, next, "pi_delete"); /* Recursive call */
     }
-    case 0:
-      GPR_UNREACHABLE_CODE(return );
   }
 }
 
@@ -488,11 +520,20 @@
   pi->fd_capacity = 0;
   pi->fds = NULL;
   pi->epoll_fd = -1;
-  pi->workqueue = NULL;
+
+  gpr_mu_init(&pi->workqueue_read_mu);
+  gpr_mpscq_init(&pi->workqueue_items);
+  gpr_atm_rel_store(&pi->workqueue_item_count, 0);
 
   gpr_atm_rel_store(&pi->ref_count, 0);
+  gpr_atm_rel_store(&pi->poller_count, 0);
   gpr_atm_rel_store(&pi->merged_to, (gpr_atm)NULL);
 
+  if (!append_error(error, grpc_wakeup_fd_init(&pi->workqueue_wakeup_fd),
+                    err_desc)) {
+    goto done;
+  }
+
   pi->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
 
   if (pi->epoll_fd < 0) {
@@ -501,26 +542,14 @@
   }
 
   polling_island_add_wakeup_fd_locked(pi, &grpc_global_wakeup_fd, error);
+  polling_island_add_wakeup_fd_locked(pi, &pi->workqueue_wakeup_fd, error);
 
   if (initial_fd != NULL) {
     polling_island_add_fds_locked(pi, &initial_fd, 1, true, error);
   }
 
-  if (append_error(error, grpc_workqueue_create(exec_ctx, &pi->workqueue),
-                   err_desc) &&
-      *error == GRPC_ERROR_NONE) {
-    polling_island_add_fds_locked(pi, &pi->workqueue->wakeup_read_fd, 1, true,
-                                  error);
-    GPR_ASSERT(pi->workqueue->wakeup_read_fd->polling_island == NULL);
-    pi->workqueue->wakeup_read_fd->polling_island = pi;
-    PI_ADD_REF(pi, "fd");
-  }
-
 done:
   if (*error != GRPC_ERROR_NONE) {
-    if (pi->workqueue != NULL) {
-      GRPC_WORKQUEUE_UNREF(exec_ctx, pi->workqueue, "polling_island");
-    }
     polling_island_delete(exec_ctx, pi);
     pi = NULL;
   }
@@ -533,7 +562,11 @@
   if (pi->epoll_fd >= 0) {
     close(pi->epoll_fd);
   }
+  GPR_ASSERT(gpr_atm_no_barrier_load(&pi->workqueue_item_count) == 0);
+  gpr_mu_destroy(&pi->workqueue_read_mu);
+  gpr_mpscq_destroy(&pi->workqueue_items);
   gpr_mu_destroy(&pi->mu);
+  grpc_wakeup_fd_destroy(&pi->workqueue_wakeup_fd);
   gpr_free(pi->fds);
   gpr_free(pi);
 }
@@ -678,6 +711,45 @@
   }
 }
 
+static void workqueue_maybe_wakeup(polling_island *pi) {
+  /* If this thread is the current poller, then it may be that it's about to
+     decrement the current poller count, so we need to look past this thread */
+  bool is_current_poller = (g_current_thread_polling_island == pi);
+  gpr_atm min_current_pollers_for_wakeup = is_current_poller ? 1 : 0;
+  gpr_atm current_pollers = gpr_atm_no_barrier_load(&pi->poller_count);
+  /* Only issue a wakeup if it's likely that some poller could come in and take
+     it right now. Note that since we do an anticipatory mpscq_pop every poll
+     loop, it's ok if we miss the wakeup here, as we'll get the work item when
+     the next poller enters anyway. */
+  if (current_pollers > min_current_pollers_for_wakeup) {
+    GRPC_LOG_IF_ERROR("workqueue_wakeup_fd",
+                      grpc_wakeup_fd_wakeup(&pi->workqueue_wakeup_fd));
+  }
+}
+
+static void workqueue_move_items_to_parent(polling_island *q) {
+  polling_island *p = (polling_island *)gpr_atm_no_barrier_load(&q->merged_to);
+  if (p == NULL) {
+    return;
+  }
+  gpr_mu_lock(&q->workqueue_read_mu);
+  int num_added = 0;
+  while (gpr_atm_no_barrier_load(&q->workqueue_item_count) > 0) {
+    gpr_mpscq_node *n = gpr_mpscq_pop(&q->workqueue_items);
+    if (n != NULL) {
+      gpr_atm_no_barrier_fetch_add(&q->workqueue_item_count, -1);
+      gpr_atm_no_barrier_fetch_add(&p->workqueue_item_count, 1);
+      gpr_mpscq_push(&p->workqueue_items, n);
+      num_added++;
+    }
+  }
+  gpr_mu_unlock(&q->workqueue_read_mu);
+  if (num_added > 0) {
+    workqueue_maybe_wakeup(p);
+  }
+  workqueue_move_items_to_parent(p);
+}
+
 static polling_island *polling_island_merge(polling_island *p,
                                             polling_island *q,
                                             grpc_error **error) {
@@ -702,6 +774,8 @@
     /* Add the 'merged_to' link from p --> q */
     gpr_atm_rel_store(&p->merged_to, (gpr_atm)q);
     PI_ADD_REF(q, "pi_merge"); /* To account for the new incoming ref from p */
+
+    workqueue_move_items_to_parent(q);
   }
   /* else if p == q, nothing needs to be done */
 
@@ -712,6 +786,26 @@
   return q;
 }
 
+static void workqueue_enqueue(grpc_exec_ctx *exec_ctx,
+                              grpc_workqueue *workqueue, grpc_closure *closure,
+                              grpc_error *error) {
+  GPR_TIMER_BEGIN("workqueue.enqueue", 0);
+  /* take a ref to the workqueue: otherwise it can happen that whatever events
+   * this kicks off ends up destroying the workqueue before this function
+   * completes */
+  GRPC_WORKQUEUE_REF(workqueue, "enqueue");
+  polling_island *pi = (polling_island *)workqueue;
+  gpr_atm last = gpr_atm_no_barrier_fetch_add(&pi->workqueue_item_count, 1);
+  closure->error_data.error = error;
+  gpr_mpscq_push(&pi->workqueue_items, &closure->next_data.atm_next);
+  if (last == 0) {
+    workqueue_maybe_wakeup(pi);
+  }
+  workqueue_move_items_to_parent(pi);
+  GRPC_WORKQUEUE_UNREF(exec_ctx, workqueue, "enqueue");
+  GPR_TIMER_END("workqueue.enqueue", 0);
+}
+
 static grpc_error *polling_island_global_init() {
   grpc_error *error = GRPC_ERROR_NONE;
 
@@ -1042,11 +1136,8 @@
 
 static grpc_workqueue *fd_get_workqueue(grpc_fd *fd) {
   gpr_mu_lock(&fd->mu);
-  grpc_workqueue *workqueue = NULL;
-  if (fd->polling_island != NULL) {
-    workqueue =
-        GRPC_WORKQUEUE_REF(fd->polling_island->workqueue, "get_workqueue");
-  }
+  grpc_workqueue *workqueue = GRPC_WORKQUEUE_REF(
+      (grpc_workqueue *)fd->polling_island, "fd_get_workqueue");
   gpr_mu_unlock(&fd->mu);
   return workqueue;
 }
@@ -1299,7 +1390,29 @@
   GPR_ASSERT(pollset->polling_island == NULL);
 }
 
-#define GRPC_EPOLL_MAX_EVENTS 1000
+static bool maybe_do_workqueue_work(grpc_exec_ctx *exec_ctx,
+                                    polling_island *pi) {
+  if (gpr_mu_trylock(&pi->workqueue_read_mu)) {
+    gpr_mpscq_node *n = gpr_mpscq_pop(&pi->workqueue_items);
+    gpr_mu_unlock(&pi->workqueue_read_mu);
+    if (n != NULL) {
+      if (gpr_atm_full_fetch_add(&pi->workqueue_item_count, -1) > 1) {
+        workqueue_maybe_wakeup(pi);
+      }
+      grpc_closure *c = (grpc_closure *)n;
+      grpc_closure_run(exec_ctx, c, c->error_data.error);
+      return true;
+    } else if (gpr_atm_no_barrier_load(&pi->workqueue_item_count) > 0) {
+      /* n == NULL might mean there's work but it's not available to be popped
+       * yet - try to ensure another workqueue wakes up to check shortly if so
+       */
+      workqueue_maybe_wakeup(pi);
+    }
+  }
+  return false;
+}
+
+#define GRPC_EPOLL_MAX_EVENTS 100
 /* Note: sig_mask contains the signal mask to use *during* epoll_wait() */
 static void pollset_work_and_unlock(grpc_exec_ctx *exec_ctx,
                                     grpc_pollset *pollset,
@@ -1354,7 +1467,13 @@
   PI_ADD_REF(pi, "ps_work");
   gpr_mu_unlock(&pollset->mu);
 
-  do {
+  /* If we get some workqueue work to do, it might end up completing an item on
+     the completion queue, so there's no need to poll... so we skip that and
+     redo the complete loop to verify */
+  if (!maybe_do_workqueue_work(exec_ctx, pi)) {
+    gpr_atm_no_barrier_fetch_add(&pi->poller_count, 1);
+    g_current_thread_polling_island = pi;
+
     GRPC_SCHEDULING_START_BLOCKING_REGION;
     ep_rv = epoll_pwait(epoll_fd, ep_ev, GRPC_EPOLL_MAX_EVENTS, timeout_ms,
                         sig_mask);
@@ -1386,6 +1505,11 @@
         append_error(error,
                      grpc_wakeup_fd_consume_wakeup(&grpc_global_wakeup_fd),
                      err_desc);
+      } else if (data_ptr == &pi->workqueue_wakeup_fd) {
+        append_error(error,
+                     grpc_wakeup_fd_consume_wakeup(&grpc_global_wakeup_fd),
+                     err_desc);
+        maybe_do_workqueue_work(exec_ctx, pi);
       } else if (data_ptr == &polling_island_wakeup_fd) {
         GRPC_POLLING_TRACE(
             "pollset_work: pollset: %p, worker: %p polling island (epoll_fd: "
@@ -1408,7 +1532,10 @@
         }
       }
     }
-  } while (ep_rv == GRPC_EPOLL_MAX_EVENTS);
+
+    g_current_thread_polling_island = NULL;
+    gpr_atm_no_barrier_fetch_add(&pi->poller_count, -1);
+  }
 
   GPR_ASSERT(pi != NULL);
 
@@ -1868,6 +1995,10 @@
 
     .kick_poller = kick_poller,
 
+    .workqueue_ref = workqueue_ref,
+    .workqueue_unref = workqueue_unref,
+    .workqueue_enqueue = workqueue_enqueue,
+
     .shutdown_engine = shutdown_engine,
 };
 
@@ -1892,6 +2023,10 @@
     return NULL;
   }
 
+  if (!grpc_has_wakeup_fd()) {
+    return NULL;
+  }
+
   if (!is_epoll_available()) {
     return NULL;
   }
diff --git a/src/core/lib/iomgr/ev_poll_and_epoll_posix.c b/src/core/lib/iomgr/ev_poll_and_epoll_posix.c
index c2107e5..1829440 100644
--- a/src/core/lib/iomgr/ev_poll_and_epoll_posix.c
+++ b/src/core/lib/iomgr/ev_poll_and_epoll_posix.c
@@ -1989,6 +1989,32 @@
 }
 
 /*******************************************************************************
+ * workqueue stubs
+ */
+
+#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
+static grpc_workqueue *workqueue_ref(grpc_workqueue *workqueue,
+                                     const char *file, int line,
+                                     const char *reason) {
+  return workqueue;
+}
+static void workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
+                            const char *file, int line, const char *reason) {}
+#else
+static grpc_workqueue *workqueue_ref(grpc_workqueue *workqueue) {
+  return workqueue;
+}
+static void workqueue_unref(grpc_exec_ctx *exec_ctx,
+                            grpc_workqueue *workqueue) {}
+#endif
+
+static void workqueue_enqueue(grpc_exec_ctx *exec_ctx,
+                              grpc_workqueue *workqueue, grpc_closure *closure,
+                              grpc_error *error) {
+  grpc_exec_ctx_sched(exec_ctx, closure, error, NULL);
+}
+
+/*******************************************************************************
  * event engine binding
  */
 
@@ -2029,6 +2055,10 @@
 
     .kick_poller = kick_poller,
 
+    .workqueue_ref = workqueue_ref,
+    .workqueue_unref = workqueue_unref,
+    .workqueue_enqueue = workqueue_enqueue,
+
     .shutdown_engine = shutdown_engine,
 };
 
diff --git a/src/core/lib/iomgr/ev_poll_posix.c b/src/core/lib/iomgr/ev_poll_posix.c
index 16a5e30..27e966c 100644
--- a/src/core/lib/iomgr/ev_poll_posix.c
+++ b/src/core/lib/iomgr/ev_poll_posix.c
@@ -47,10 +47,12 @@
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
+#include <grpc/support/thd.h>
 #include <grpc/support/tls.h>
 #include <grpc/support/useful.h>
 
 #include "src/core/lib/iomgr/iomgr_internal.h"
+#include "src/core/lib/iomgr/wakeup_fd_cv.h"
 #include "src/core/lib/iomgr/wakeup_fd_posix.h"
 #include "src/core/lib/profiling/timers.h"
 #include "src/core/lib/support/block_annotate.h"
@@ -246,6 +248,28 @@
 };
 
 /*******************************************************************************
+ * condition variable polling definitions
+ */
+
+#define CV_POLL_PERIOD_MS 1000
+#define CV_DEFAULT_TABLE_SIZE 16
+
+typedef enum poll_status_t { INPROGRESS, COMPLETED, CANCELLED } poll_status_t;
+
+typedef struct poll_args {
+  gpr_refcount refcount;
+  gpr_cv *cv;
+  struct pollfd *fds;
+  nfds_t nfds;
+  int timeout;
+  int retval;
+  int err;
+  gpr_atm status;
+} poll_args;
+
+cv_fd_table g_cvfds;
+
+/*******************************************************************************
  * fd_posix.c
  */
 
@@ -961,8 +985,15 @@
         if (errno != EINTR) {
           work_combine_error(&error, GRPC_OS_ERROR(errno, "poll"));
         }
+
         for (i = 2; i < pfd_count; i++) {
-          fd_end_poll(exec_ctx, &watchers[i], 0, 0, NULL);
+          if (watchers[i].fd == NULL) {
+            fd_end_poll(exec_ctx, &watchers[i], 0, 0, NULL);
+          } else {
+            // Wake up all the file descriptors, if we have an invalid one
+            // we can identify it on the next pollset_work()
+            fd_end_poll(exec_ctx, &watchers[i], 1, 1, pollset);
+          }
         }
       } else if (r == 0) {
         for (i = 2; i < pfd_count; i++) {
@@ -1236,10 +1267,237 @@
 }
 
 /*******************************************************************************
+ * workqueue stubs
+ */
+
+#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
+static grpc_workqueue *workqueue_ref(grpc_workqueue *workqueue,
+                                     const char *file, int line,
+                                     const char *reason) {
+  return workqueue;
+}
+static void workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
+                            const char *file, int line, const char *reason) {}
+#else
+static grpc_workqueue *workqueue_ref(grpc_workqueue *workqueue) {
+  return workqueue;
+}
+static void workqueue_unref(grpc_exec_ctx *exec_ctx,
+                            grpc_workqueue *workqueue) {}
+#endif
+
+static void workqueue_enqueue(grpc_exec_ctx *exec_ctx,
+                              grpc_workqueue *workqueue, grpc_closure *closure,
+                              grpc_error *error) {
+  grpc_exec_ctx_sched(exec_ctx, closure, error, NULL);
+}
+
+/*******************************************************************************
+ * Condition Variable polling extensions
+ */
+
+static void decref_poll_args(poll_args *args) {
+  if (gpr_unref(&args->refcount)) {
+    gpr_free(args->fds);
+    gpr_cv_destroy(args->cv);
+    gpr_free(args->cv);
+    gpr_free(args);
+  }
+}
+
+// Poll in a background thread
+static void run_poll(void *arg) {
+  int timeout, retval;
+  poll_args *pargs = (poll_args *)arg;
+  while (gpr_atm_no_barrier_load(&pargs->status) == INPROGRESS) {
+    if (pargs->timeout < 0) {
+      timeout = CV_POLL_PERIOD_MS;
+    } else {
+      timeout = GPR_MIN(CV_POLL_PERIOD_MS, pargs->timeout);
+      pargs->timeout -= timeout;
+    }
+    retval = g_cvfds.poll(pargs->fds, pargs->nfds, timeout);
+    if (retval != 0 || pargs->timeout == 0) {
+      pargs->retval = retval;
+      pargs->err = errno;
+      break;
+    }
+  }
+  gpr_mu_lock(&g_cvfds.mu);
+  if (gpr_atm_no_barrier_load(&pargs->status) == INPROGRESS) {
+    // Signal main thread that the poll completed
+    gpr_atm_no_barrier_store(&pargs->status, COMPLETED);
+    gpr_cv_signal(pargs->cv);
+  }
+  decref_poll_args(pargs);
+  g_cvfds.pollcount--;
+  if (g_cvfds.shutdown && g_cvfds.pollcount == 0) {
+    gpr_cv_signal(&g_cvfds.shutdown_complete);
+  }
+  gpr_mu_unlock(&g_cvfds.mu);
+}
+
+// This function overrides poll() to handle condition variable wakeup fds
+static int cvfd_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
+  unsigned int i;
+  int res, idx;
+  gpr_cv *pollcv;
+  cv_node *cvn, *prev;
+  nfds_t nsockfds = 0;
+  gpr_thd_id t_id;
+  gpr_thd_options opt;
+  poll_args *pargs = NULL;
+  gpr_mu_lock(&g_cvfds.mu);
+  pollcv = gpr_malloc(sizeof(gpr_cv));
+  gpr_cv_init(pollcv);
+  for (i = 0; i < nfds; i++) {
+    fds[i].revents = 0;
+    if (fds[i].fd < 0 && (fds[i].events & POLLIN)) {
+      idx = FD_TO_IDX(fds[i].fd);
+      cvn = gpr_malloc(sizeof(cv_node));
+      cvn->cv = pollcv;
+      cvn->next = g_cvfds.cvfds[idx].cvs;
+      g_cvfds.cvfds[idx].cvs = cvn;
+      // We should return immediately if there are pending events,
+      // but we still need to call poll() to check for socket events
+      if (g_cvfds.cvfds[idx].is_set) {
+        timeout = 0;
+      }
+    } else if (fds[i].fd >= 0) {
+      nsockfds++;
+    }
+  }
+
+  if (nsockfds > 0) {
+    pargs = gpr_malloc(sizeof(struct poll_args));
+    // Both the main thread and calling thread get a reference
+    gpr_ref_init(&pargs->refcount, 2);
+    pargs->cv = pollcv;
+    pargs->fds = gpr_malloc(sizeof(struct pollfd) * nsockfds);
+    pargs->nfds = nsockfds;
+    pargs->timeout = timeout;
+    pargs->retval = 0;
+    pargs->err = 0;
+    gpr_atm_no_barrier_store(&pargs->status, INPROGRESS);
+    idx = 0;
+    for (i = 0; i < nfds; i++) {
+      if (fds[i].fd >= 0) {
+        pargs->fds[idx].fd = fds[i].fd;
+        pargs->fds[idx].events = fds[i].events;
+        pargs->fds[idx].revents = 0;
+        idx++;
+      }
+    }
+    g_cvfds.pollcount++;
+    opt = gpr_thd_options_default();
+    gpr_thd_options_set_detached(&opt);
+    gpr_thd_new(&t_id, &run_poll, pargs, &opt);
+    // We want the poll() thread to trigger the deadline, so wait forever here
+    gpr_cv_wait(pollcv, &g_cvfds.mu, gpr_inf_future(GPR_CLOCK_MONOTONIC));
+    if (gpr_atm_no_barrier_load(&pargs->status) == COMPLETED) {
+      res = pargs->retval;
+      errno = pargs->err;
+    } else {
+      res = 0;
+      errno = 0;
+      gpr_atm_no_barrier_store(&pargs->status, CANCELLED);
+    }
+  } else {
+    gpr_timespec deadline = gpr_now(GPR_CLOCK_REALTIME);
+    deadline =
+        gpr_time_add(deadline, gpr_time_from_millis(timeout, GPR_TIMESPAN));
+    gpr_cv_wait(pollcv, &g_cvfds.mu, deadline);
+    res = 0;
+  }
+
+  idx = 0;
+  for (i = 0; i < nfds; i++) {
+    if (fds[i].fd < 0 && (fds[i].events & POLLIN)) {
+      cvn = g_cvfds.cvfds[FD_TO_IDX(fds[i].fd)].cvs;
+      prev = NULL;
+      while (cvn->cv != pollcv) {
+        prev = cvn;
+        cvn = cvn->next;
+        GPR_ASSERT(cvn);
+      }
+      if (!prev) {
+        g_cvfds.cvfds[FD_TO_IDX(fds[i].fd)].cvs = cvn->next;
+      } else {
+        prev->next = cvn->next;
+      }
+      gpr_free(cvn);
+
+      if (g_cvfds.cvfds[FD_TO_IDX(fds[i].fd)].is_set) {
+        fds[i].revents = POLLIN;
+        if (res >= 0) res++;
+      }
+    } else if (fds[i].fd >= 0 &&
+               gpr_atm_no_barrier_load(&pargs->status) == COMPLETED) {
+      fds[i].revents = pargs->fds[idx].revents;
+      idx++;
+    }
+  }
+
+  if (pargs) {
+    decref_poll_args(pargs);
+  } else {
+    gpr_cv_destroy(pollcv);
+    gpr_free(pollcv);
+  }
+  gpr_mu_unlock(&g_cvfds.mu);
+
+  return res;
+}
+
+static void global_cv_fd_table_init() {
+  gpr_mu_init(&g_cvfds.mu);
+  gpr_mu_lock(&g_cvfds.mu);
+  gpr_cv_init(&g_cvfds.shutdown_complete);
+  g_cvfds.shutdown = 0;
+  g_cvfds.pollcount = 0;
+  g_cvfds.size = CV_DEFAULT_TABLE_SIZE;
+  g_cvfds.cvfds = gpr_malloc(sizeof(fd_node) * CV_DEFAULT_TABLE_SIZE);
+  g_cvfds.free_fds = NULL;
+  for (int i = 0; i < CV_DEFAULT_TABLE_SIZE; i++) {
+    g_cvfds.cvfds[i].is_set = 0;
+    g_cvfds.cvfds[i].cvs = NULL;
+    g_cvfds.cvfds[i].next_free = g_cvfds.free_fds;
+    g_cvfds.free_fds = &g_cvfds.cvfds[i];
+  }
+  // Override the poll function with one that supports cvfds
+  g_cvfds.poll = grpc_poll_function;
+  grpc_poll_function = &cvfd_poll;
+  gpr_mu_unlock(&g_cvfds.mu);
+}
+
+static void global_cv_fd_table_shutdown() {
+  gpr_mu_lock(&g_cvfds.mu);
+  g_cvfds.shutdown = 1;
+  // Attempt to wait for all abandoned poll() threads to terminate
+  // Not doing so will result in reported memory leaks
+  if (g_cvfds.pollcount > 0) {
+    int res = gpr_cv_wait(&g_cvfds.shutdown_complete, &g_cvfds.mu,
+                          gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
+                                       gpr_time_from_seconds(3, GPR_TIMESPAN)));
+    GPR_ASSERT(res == 0);
+  }
+  gpr_cv_destroy(&g_cvfds.shutdown_complete);
+  grpc_poll_function = g_cvfds.poll;
+  gpr_free(g_cvfds.cvfds);
+  gpr_mu_unlock(&g_cvfds.mu);
+  gpr_mu_destroy(&g_cvfds.mu);
+}
+
+/*******************************************************************************
  * event engine binding
  */
 
-static void shutdown_engine(void) { pollset_global_shutdown(); }
+static void shutdown_engine(void) {
+  pollset_global_shutdown();
+  if (grpc_cv_wakeup_fds_enabled()) {
+    global_cv_fd_table_shutdown();
+  }
+}
 
 static const grpc_event_engine_vtable vtable = {
     .pollset_size = sizeof(grpc_pollset),
@@ -1273,14 +1531,32 @@
 
     .kick_poller = kick_poller,
 
+    .workqueue_ref = workqueue_ref,
+    .workqueue_unref = workqueue_unref,
+    .workqueue_enqueue = workqueue_enqueue,
+
     .shutdown_engine = shutdown_engine,
 };
 
 const grpc_event_engine_vtable *grpc_init_poll_posix(void) {
+  if (!grpc_has_wakeup_fd()) {
+    return NULL;
+  }
   if (!GRPC_LOG_IF_ERROR("pollset_global_init", pollset_global_init())) {
     return NULL;
   }
   return &vtable;
 }
 
+const grpc_event_engine_vtable *grpc_init_poll_cv_posix(void) {
+  global_cv_fd_table_init();
+  grpc_enable_cv_wakeup_fds(1);
+  if (!GRPC_LOG_IF_ERROR("pollset_global_init", pollset_global_init())) {
+    global_cv_fd_table_shutdown();
+    grpc_enable_cv_wakeup_fds(0);
+    return NULL;
+  }
+  return &vtable;
+}
+
 #endif
diff --git a/src/core/lib/iomgr/ev_poll_posix.h b/src/core/lib/iomgr/ev_poll_posix.h
index 291736a..202ffca 100644
--- a/src/core/lib/iomgr/ev_poll_posix.h
+++ b/src/core/lib/iomgr/ev_poll_posix.h
@@ -37,5 +37,6 @@
 #include "src/core/lib/iomgr/ev_posix.h"
 
 const grpc_event_engine_vtable *grpc_init_poll_posix(void);
+const grpc_event_engine_vtable *grpc_init_poll_cv_posix(void);
 
 #endif /* GRPC_CORE_LIB_IOMGR_EV_POLL_POSIX_H */
diff --git a/src/core/lib/iomgr/ev_posix.c b/src/core/lib/iomgr/ev_posix.c
index 6536672..9857b0b 100644
--- a/src/core/lib/iomgr/ev_posix.c
+++ b/src/core/lib/iomgr/ev_posix.c
@@ -66,6 +66,7 @@
 static const event_engine_factory g_factories[] = {
     {"epoll", grpc_init_epoll_linux},
     {"poll", grpc_init_poll_posix},
+    {"poll-cv", grpc_init_poll_cv_posix},
     {"legacy", grpc_init_poll_and_epoll_posix},
 };
 
@@ -258,4 +259,27 @@
 
 grpc_error *grpc_kick_poller(void) { return g_event_engine->kick_poller(); }
 
+#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
+grpc_workqueue *grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file,
+                                   int line, const char *reason) {
+  return g_event_engine->workqueue_ref(workqueue, file, line, reason);
+}
+void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
+                          const char *file, int line, const char *reason) {
+  g_event_engine->workqueue_unref(exec_ctx, workqueue, file, line, reason);
+}
+#else
+grpc_workqueue *grpc_workqueue_ref(grpc_workqueue *workqueue) {
+  return g_event_engine->workqueue_ref(workqueue);
+}
+void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue) {
+  g_event_engine->workqueue_unref(exec_ctx, workqueue);
+}
+#endif
+
+void grpc_workqueue_enqueue(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
+                            grpc_closure *closure, grpc_error *error) {
+  g_event_engine->workqueue_enqueue(exec_ctx, workqueue, closure, error);
+}
+
 #endif  // GPR_POSIX_SOCKET
diff --git a/src/core/lib/iomgr/ev_posix.h b/src/core/lib/iomgr/ev_posix.h
index c2aa175..2fdef06 100644
--- a/src/core/lib/iomgr/ev_posix.h
+++ b/src/core/lib/iomgr/ev_posix.h
@@ -40,6 +40,7 @@
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/pollset_set.h"
 #include "src/core/lib/iomgr/wakeup_fd_posix.h"
+#include "src/core/lib/iomgr/workqueue.h"
 
 typedef struct grpc_fd grpc_fd;
 
@@ -95,6 +96,18 @@
   grpc_error *(*kick_poller)(void);
 
   void (*shutdown_engine)(void);
+
+#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
+  grpc_workqueue *(*workqueue_ref)(grpc_workqueue *workqueue, const char *file,
+                                   int line, const char *reason);
+  void (*workqueue_unref)(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
+                          const char *file, int line, const char *reason);
+#else
+  grpc_workqueue *(*workqueue_ref)(grpc_workqueue *workqueue);
+  void (*workqueue_unref)(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue);
+#endif
+  void (*workqueue_enqueue)(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
+                            grpc_closure *closure, grpc_error *error);
 } grpc_event_engine_vtable;
 
 void grpc_event_engine_init(void);
diff --git a/src/core/lib/iomgr/exec_ctx.c b/src/core/lib/iomgr/exec_ctx.c
index ac7785e..604713e 100644
--- a/src/core/lib/iomgr/exec_ctx.c
+++ b/src/core/lib/iomgr/exec_ctx.c
@@ -37,6 +37,7 @@
 #include <grpc/support/sync.h>
 #include <grpc/support/thd.h>
 
+#include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/workqueue.h"
 #include "src/core/lib/profiling/timers.h"
 
@@ -60,18 +61,43 @@
 bool grpc_exec_ctx_flush(grpc_exec_ctx *exec_ctx) {
   bool did_something = 0;
   GPR_TIMER_BEGIN("grpc_exec_ctx_flush", 0);
-  while (!grpc_closure_list_empty(exec_ctx->closure_list)) {
-    grpc_closure *c = exec_ctx->closure_list.head;
-    exec_ctx->closure_list.head = exec_ctx->closure_list.tail = NULL;
-    while (c != NULL) {
-      grpc_closure *next = c->next_data.next;
-      grpc_error *error = c->error;
-      did_something = true;
-      GPR_TIMER_BEGIN("grpc_exec_ctx_flush.cb", 0);
+  for (;;) {
+    if (!grpc_closure_list_empty(exec_ctx->closure_list)) {
+      grpc_closure *c = exec_ctx->closure_list.head;
+      exec_ctx->closure_list.head = exec_ctx->closure_list.tail = NULL;
+      while (c != NULL) {
+        grpc_closure *next = c->next_data.next;
+        did_something = true;
+        grpc_closure_run(exec_ctx, c, c->error_data.error);
+        c = next;
+      }
+    } else if (!grpc_combiner_continue_exec_ctx(exec_ctx)) {
+      break;
+    }
+  }
+  GPR_ASSERT(exec_ctx->active_combiner == NULL);
+  if (exec_ctx->stealing_from_workqueue != NULL) {
+    if (grpc_exec_ctx_ready_to_finish(exec_ctx)) {
+      grpc_workqueue_enqueue(exec_ctx, exec_ctx->stealing_from_workqueue,
+                             exec_ctx->stolen_closure,
+                             exec_ctx->stolen_closure->error_data.error);
+      GRPC_WORKQUEUE_UNREF(exec_ctx, exec_ctx->stealing_from_workqueue,
+                           "exec_ctx_sched");
+      exec_ctx->stealing_from_workqueue = NULL;
+      exec_ctx->stolen_closure = NULL;
+    } else {
+      grpc_closure *c = exec_ctx->stolen_closure;
+      GRPC_WORKQUEUE_UNREF(exec_ctx, exec_ctx->stealing_from_workqueue,
+                           "exec_ctx_sched");
+      exec_ctx->stealing_from_workqueue = NULL;
+      exec_ctx->stolen_closure = NULL;
+      grpc_error *error = c->error_data.error;
+      GPR_TIMER_BEGIN("grpc_exec_ctx_flush.stolen_cb", 0);
       c->cb(exec_ctx, c->cb_arg, error);
       GRPC_ERROR_UNREF(error);
-      GPR_TIMER_END("grpc_exec_ctx_flush.cb", 0);
-      c = next;
+      GPR_TIMER_END("grpc_exec_ctx_flush.stolen_cb", 0);
+      grpc_exec_ctx_flush(exec_ctx);
+      did_something = true;
     }
   }
   GPR_TIMER_END("grpc_exec_ctx_flush", 0);
@@ -86,12 +112,25 @@
 void grpc_exec_ctx_sched(grpc_exec_ctx *exec_ctx, grpc_closure *closure,
                          grpc_error *error,
                          grpc_workqueue *offload_target_or_null) {
+  GPR_TIMER_BEGIN("grpc_exec_ctx_sched", 0);
   if (offload_target_or_null == NULL) {
     grpc_closure_list_append(&exec_ctx->closure_list, closure, error);
-  } else {
+  } else if (exec_ctx->stealing_from_workqueue == NULL) {
+    exec_ctx->stealing_from_workqueue = offload_target_or_null;
+    closure->error_data.error = error;
+    exec_ctx->stolen_closure = closure;
+  } else if (exec_ctx->stealing_from_workqueue != offload_target_or_null) {
     grpc_workqueue_enqueue(exec_ctx, offload_target_or_null, closure, error);
     GRPC_WORKQUEUE_UNREF(exec_ctx, offload_target_or_null, "exec_ctx_sched");
+  } else { /* stealing_from_workqueue == offload_target_or_null */
+    grpc_workqueue_enqueue(exec_ctx, offload_target_or_null,
+                           exec_ctx->stolen_closure,
+                           exec_ctx->stolen_closure->error_data.error);
+    closure->error_data.error = error;
+    exec_ctx->stolen_closure = closure;
+    GRPC_WORKQUEUE_UNREF(exec_ctx, offload_target_or_null, "exec_ctx_sched");
   }
+  GPR_TIMER_END("grpc_exec_ctx_sched", 0);
 }
 
 void grpc_exec_ctx_enqueue_list(grpc_exec_ctx *exec_ctx,
diff --git a/src/core/lib/iomgr/exec_ctx.h b/src/core/lib/iomgr/exec_ctx.h
index 4d20ecf..7e50cb9 100644
--- a/src/core/lib/iomgr/exec_ctx.h
+++ b/src/core/lib/iomgr/exec_ctx.h
@@ -66,15 +66,33 @@
 #ifndef GRPC_EXECUTION_CONTEXT_SANITIZER
 struct grpc_exec_ctx {
   grpc_closure_list closure_list;
+  /** The workqueue we're stealing work from.
+      As items are queued to the execution context, we try to steal one
+      workqueue item and execute it inline (assuming the exec_ctx is not
+      finished) - doing so does not invalidate the workqueue's contract, and
+      provides a small latency win in cases where we get a hit */
+  grpc_workqueue *stealing_from_workqueue;
+  /** The workqueue item that was stolen from the workqueue above. When new
+      items are scheduled to be offloaded to that workqueue, we need to update
+      this like a 1-deep fifo to maintain the invariant that workqueue items
+      queued by one thread are started in order */
+  grpc_closure *stolen_closure;
   /** currently active combiner: updated only via combiner.c */
   grpc_combiner *active_combiner;
+  /** last active combiner in the active combiner list */
+  grpc_combiner *last_combiner;
   bool cached_ready_to_finish;
   void *check_ready_to_finish_arg;
   bool (*check_ready_to_finish)(grpc_exec_ctx *exec_ctx, void *arg);
 };
 
+/* initializer for grpc_exec_ctx:
+   prefer to use GRPC_EXEC_CTX_INIT whenever possible */
 #define GRPC_EXEC_CTX_INIT_WITH_FINISH_CHECK(finish_check, finish_check_arg) \
-  { GRPC_CLOSURE_LIST_INIT, NULL, false, finish_check_arg, finish_check }
+  {                                                                          \
+    GRPC_CLOSURE_LIST_INIT, NULL, NULL, NULL, NULL, false, finish_check_arg, \
+        finish_check                                                         \
+  }
 #else
 struct grpc_exec_ctx {
   bool cached_ready_to_finish;
@@ -85,8 +103,10 @@
   { false, finish_check_arg, finish_check }
 #endif
 
+/* initialize an execution context at the top level of an API call into grpc
+   (this is safe to use elsewhere, though possibly not as efficient) */
 #define GRPC_EXEC_CTX_INIT \
-  GRPC_EXEC_CTX_INIT_WITH_FINISH_CHECK(grpc_never_ready_to_finish, NULL)
+  GRPC_EXEC_CTX_INIT_WITH_FINISH_CHECK(grpc_always_ready_to_finish, NULL)
 
 /** Flush any work that has been enqueued onto this grpc_exec_ctx.
  *  Caller must guarantee that no interfering locks are held.
diff --git a/src/core/lib/iomgr/iomgr.c b/src/core/lib/iomgr/iomgr.c
index d67d388..4fd83e0 100644
--- a/src/core/lib/iomgr/iomgr.c
+++ b/src/core/lib/iomgr/iomgr.c
@@ -112,6 +112,14 @@
       continue;
     }
     if (g_root_object.next != &g_root_object) {
+      if (grpc_iomgr_abort_on_leaks()) {
+        gpr_log(GPR_DEBUG, "Failed to free %" PRIuPTR
+                           " iomgr objects before shutdown deadline: "
+                           "memory leaks are likely",
+                count_objects());
+        dump_objects("LEAKED");
+        abort();
+      }
       gpr_timespec short_deadline = gpr_time_add(
           gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_millis(100, GPR_TIMESPAN));
       if (gpr_cv_wait(&g_rcv, &g_mu, short_deadline)) {
@@ -122,9 +130,6 @@
                                "memory leaks are likely",
                     count_objects());
             dump_objects("LEAKED");
-            if (grpc_iomgr_abort_on_leaks()) {
-              abort();
-            }
           }
           break;
         }
diff --git a/src/core/lib/iomgr/tcp_posix.c b/src/core/lib/iomgr/tcp_posix.c
index 9276772..00fd776 100644
--- a/src/core/lib/iomgr/tcp_posix.c
+++ b/src/core/lib/iomgr/tcp_posix.c
@@ -177,7 +177,7 @@
 
   tcp->read_cb = NULL;
   tcp->incoming_buffer = NULL;
-  grpc_exec_ctx_sched(exec_ctx, cb, error, NULL);
+  grpc_closure_run(exec_ctx, cb, error);
 }
 
 #define MAX_READ_IOVEC 4
@@ -209,11 +209,11 @@
   msg.msg_controllen = 0;
   msg.msg_flags = 0;
 
-  GPR_TIMER_BEGIN("recvmsg", 1);
+  GPR_TIMER_BEGIN("recvmsg", 0);
   do {
     read_bytes = recvmsg(tcp->fd, &msg, 0);
   } while (read_bytes < 0 && errno == EINTR);
-  GPR_TIMER_END("recvmsg", 0);
+  GPR_TIMER_END("recvmsg", read_bytes >= 0);
 
   if (read_bytes < 0) {
     /* NB: After calling call_read_cb a parallel call of the read handler may
@@ -392,11 +392,8 @@
       grpc_error_free_string(str);
     }
 
-    GPR_TIMER_BEGIN("tcp_handle_write.cb", 0);
-    cb->cb(exec_ctx, cb->cb_arg, error);
-    GPR_TIMER_END("tcp_handle_write.cb", 0);
+    grpc_closure_run(exec_ctx, cb, error);
     TCP_UNREF(exec_ctx, tcp, "write");
-    GRPC_ERROR_UNREF(error);
   }
 }
 
diff --git a/src/core/lib/iomgr/wakeup_fd_cv.c b/src/core/lib/iomgr/wakeup_fd_cv.c
new file mode 100644
index 0000000..b416520
--- /dev/null
+++ b/src/core/lib/iomgr/wakeup_fd_cv.c
@@ -0,0 +1,118 @@
+/*
+ *
+ * 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/support/port_platform.h>
+
+#ifdef GPR_POSIX_WAKEUP_FD
+
+#include "src/core/lib/iomgr/wakeup_fd_cv.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/sync.h>
+#include <grpc/support/thd.h>
+#include <grpc/support/time.h>
+#include <grpc/support/useful.h>
+
+#define MAX_TABLE_RESIZE 256
+
+extern cv_fd_table g_cvfds;
+
+static grpc_error* cv_fd_init(grpc_wakeup_fd* fd_info) {
+  unsigned int i, newsize;
+  int idx;
+  gpr_mu_lock(&g_cvfds.mu);
+  if (!g_cvfds.free_fds) {
+    newsize = GPR_MIN(g_cvfds.size * 2, g_cvfds.size + MAX_TABLE_RESIZE);
+    g_cvfds.cvfds = gpr_realloc(g_cvfds.cvfds, sizeof(fd_node) * newsize);
+    for (i = g_cvfds.size; i < newsize; i++) {
+      g_cvfds.cvfds[i].is_set = 0;
+      g_cvfds.cvfds[i].cvs = NULL;
+      g_cvfds.cvfds[i].next_free = g_cvfds.free_fds;
+      g_cvfds.free_fds = &g_cvfds.cvfds[i];
+    }
+    g_cvfds.size = newsize;
+  }
+
+  idx = (int)(g_cvfds.free_fds - g_cvfds.cvfds);
+  g_cvfds.free_fds = g_cvfds.free_fds->next_free;
+  g_cvfds.cvfds[idx].cvs = NULL;
+  g_cvfds.cvfds[idx].is_set = 0;
+  fd_info->read_fd = IDX_TO_FD(idx);
+  fd_info->write_fd = -1;
+  gpr_mu_unlock(&g_cvfds.mu);
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* cv_fd_wakeup(grpc_wakeup_fd* fd_info) {
+  cv_node* cvn;
+  gpr_mu_lock(&g_cvfds.mu);
+  g_cvfds.cvfds[FD_TO_IDX(fd_info->read_fd)].is_set = 1;
+  cvn = g_cvfds.cvfds[FD_TO_IDX(fd_info->read_fd)].cvs;
+  while (cvn) {
+    gpr_cv_signal(cvn->cv);
+    cvn = cvn->next;
+  }
+  gpr_mu_unlock(&g_cvfds.mu);
+  return GRPC_ERROR_NONE;
+}
+
+static grpc_error* cv_fd_consume(grpc_wakeup_fd* fd_info) {
+  gpr_mu_lock(&g_cvfds.mu);
+  g_cvfds.cvfds[FD_TO_IDX(fd_info->read_fd)].is_set = 0;
+  gpr_mu_unlock(&g_cvfds.mu);
+  return GRPC_ERROR_NONE;
+}
+
+static void cv_fd_destroy(grpc_wakeup_fd* fd_info) {
+  if (fd_info->read_fd == 0) {
+    return;
+  }
+  gpr_mu_lock(&g_cvfds.mu);
+  // Assert that there are no active pollers
+  GPR_ASSERT(!g_cvfds.cvfds[FD_TO_IDX(fd_info->read_fd)].cvs);
+  g_cvfds.cvfds[FD_TO_IDX(fd_info->read_fd)].next_free = g_cvfds.free_fds;
+  g_cvfds.free_fds = &g_cvfds.cvfds[FD_TO_IDX(fd_info->read_fd)];
+  gpr_mu_unlock(&g_cvfds.mu);
+}
+
+static int cv_check_availability(void) { return 1; }
+
+const grpc_wakeup_fd_vtable grpc_cv_wakeup_fd_vtable = {
+    cv_fd_init, cv_fd_consume, cv_fd_wakeup, cv_fd_destroy,
+    cv_check_availability};
+
+#endif /* GPR_POSIX_WAKUP_FD */
diff --git a/src/core/lib/iomgr/wakeup_fd_cv.h b/src/core/lib/iomgr/wakeup_fd_cv.h
new file mode 100644
index 0000000..ac16be1
--- /dev/null
+++ b/src/core/lib/iomgr/wakeup_fd_cv.h
@@ -0,0 +1,80 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+/*
+ * wakeup_fd_cv uses condition variables to implement wakeup fds.
+ *
+ * It is intended for use only in cases when eventfd() and pipe() are not
+ * available.  It can only be used with the "poll" engine.
+ *
+ * Implementation:
+ * A global table of cv wakeup fds is mantained.  A cv wakeup fd is a negative
+ * file descriptor.  poll() is then run in a background thread with only the
+ * real socket fds while we wait on a condition variable trigged by either the
+ * poll() completion or a wakeup_fd() call.
+ *
+ */
+
+#ifndef GRPC_CORE_LIB_IOMGR_WAKEUP_FD_CV_H
+#define GRPC_CORE_LIB_IOMGR_WAKEUP_FD_CV_H
+
+#include <grpc/support/sync.h>
+
+#include "src/core/lib/iomgr/ev_posix.h"
+
+#define FD_TO_IDX(fd) (-(fd)-1)
+#define IDX_TO_FD(idx) (-(idx)-1)
+
+typedef struct cv_node {
+  gpr_cv* cv;
+  struct cv_node* next;
+} cv_node;
+
+typedef struct fd_node {
+  int is_set;
+  cv_node* cvs;
+  struct fd_node* next_free;
+} fd_node;
+
+typedef struct cv_fd_table {
+  gpr_mu mu;
+  int pollcount;
+  int shutdown;
+  gpr_cv shutdown_complete;
+  fd_node* cvfds;
+  fd_node* free_fds;
+  unsigned int size;
+  grpc_poll_function_type poll;
+} cv_fd_table;
+
+#endif /* GRPC_CORE_LIB_IOMGR_WAKEUP_FD_CV_H */
diff --git a/src/core/lib/iomgr/wakeup_fd_pipe.c b/src/core/lib/iomgr/wakeup_fd_pipe.c
index 4e5dbdc..d0ea216 100644
--- a/src/core/lib/iomgr/wakeup_fd_pipe.c
+++ b/src/core/lib/iomgr/wakeup_fd_pipe.c
@@ -47,11 +47,10 @@
 
 static grpc_error* pipe_init(grpc_wakeup_fd* fd_info) {
   int pipefd[2];
-  /* TODO(klempner): Make this nonfatal */
   int r = pipe(pipefd);
   if (0 != r) {
     gpr_log(GPR_ERROR, "pipe creation failed (%d): %s", errno, strerror(errno));
-    abort();
+    return GRPC_OS_ERROR(errno, "pipe");
   }
   grpc_error* err;
   err = grpc_set_socket_nonblocking(pipefd[0], 1);
@@ -95,8 +94,13 @@
 }
 
 static int pipe_check_availability(void) {
-  /* Assume that pipes are always available. */
-  return 1;
+  grpc_wakeup_fd fd;
+  if (pipe_init(&fd) == GRPC_ERROR_NONE) {
+    pipe_destroy(&fd);
+    return 1;
+  } else {
+    return 0;
+  }
 }
 
 const grpc_wakeup_fd_vtable grpc_pipe_wakeup_fd_vtable = {
diff --git a/src/core/lib/iomgr/wakeup_fd_posix.c b/src/core/lib/iomgr/wakeup_fd_posix.c
index 046208a..5c894be 100644
--- a/src/core/lib/iomgr/wakeup_fd_posix.c
+++ b/src/core/lib/iomgr/wakeup_fd_posix.c
@@ -36,37 +36,66 @@
 #ifdef GPR_POSIX_WAKEUP_FD
 
 #include <stddef.h>
+#include "src/core/lib/iomgr/wakeup_fd_cv.h"
 #include "src/core/lib/iomgr/wakeup_fd_pipe.h"
 #include "src/core/lib/iomgr/wakeup_fd_posix.h"
 
+extern grpc_wakeup_fd_vtable grpc_cv_wakeup_fd_vtable;
 static const grpc_wakeup_fd_vtable *wakeup_fd_vtable = NULL;
+
 int grpc_allow_specialized_wakeup_fd = 1;
+int grpc_allow_pipe_wakeup_fd = 1;
+
+int has_real_wakeup_fd = 1;
+int cv_wakeup_fds_enabled = 0;
 
 void grpc_wakeup_fd_global_init(void) {
   if (grpc_allow_specialized_wakeup_fd &&
       grpc_specialized_wakeup_fd_vtable.check_availability()) {
     wakeup_fd_vtable = &grpc_specialized_wakeup_fd_vtable;
-  } else {
+  } else if (grpc_allow_pipe_wakeup_fd &&
+             grpc_pipe_wakeup_fd_vtable.check_availability()) {
     wakeup_fd_vtable = &grpc_pipe_wakeup_fd_vtable;
+  } else {
+    has_real_wakeup_fd = 0;
   }
 }
 
 void grpc_wakeup_fd_global_destroy(void) { wakeup_fd_vtable = NULL; }
 
+int grpc_has_wakeup_fd(void) { return has_real_wakeup_fd; }
+
+int grpc_cv_wakeup_fds_enabled(void) { return cv_wakeup_fds_enabled; }
+
+void grpc_enable_cv_wakeup_fds(int enable) { cv_wakeup_fds_enabled = enable; }
+
 grpc_error *grpc_wakeup_fd_init(grpc_wakeup_fd *fd_info) {
+  if (cv_wakeup_fds_enabled) {
+    return grpc_cv_wakeup_fd_vtable.init(fd_info);
+  }
   return wakeup_fd_vtable->init(fd_info);
 }
 
 grpc_error *grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd *fd_info) {
+  if (cv_wakeup_fds_enabled) {
+    return grpc_cv_wakeup_fd_vtable.consume(fd_info);
+  }
   return wakeup_fd_vtable->consume(fd_info);
 }
 
 grpc_error *grpc_wakeup_fd_wakeup(grpc_wakeup_fd *fd_info) {
+  if (cv_wakeup_fds_enabled) {
+    return grpc_cv_wakeup_fd_vtable.wakeup(fd_info);
+  }
   return wakeup_fd_vtable->wakeup(fd_info);
 }
 
 void grpc_wakeup_fd_destroy(grpc_wakeup_fd *fd_info) {
-  wakeup_fd_vtable->destroy(fd_info);
+  if (cv_wakeup_fds_enabled) {
+    grpc_cv_wakeup_fd_vtable.destroy(fd_info);
+  } else {
+    wakeup_fd_vtable->destroy(fd_info);
+  }
 }
 
 #endif /* GPR_POSIX_WAKEUP_FD */
diff --git a/src/core/lib/iomgr/wakeup_fd_posix.h b/src/core/lib/iomgr/wakeup_fd_posix.h
index e269f24..71d32d9 100644
--- a/src/core/lib/iomgr/wakeup_fd_posix.h
+++ b/src/core/lib/iomgr/wakeup_fd_posix.h
@@ -71,6 +71,10 @@
  * purposes only.*/
 void grpc_wakeup_fd_global_init_force_fallback(void);
 
+int grpc_has_wakeup_fd(void);
+int grpc_cv_wakeup_fds_enabled(void);
+void grpc_enable_cv_wakeup_fds(int enable);
+
 typedef struct grpc_wakeup_fd grpc_wakeup_fd;
 
 typedef struct grpc_wakeup_fd_vtable {
@@ -88,6 +92,7 @@
 };
 
 extern int grpc_allow_specialized_wakeup_fd;
+extern int grpc_allow_pipe_wakeup_fd;
 
 #define GRPC_WAKEUP_FD_GET_READ_FD(fd_info) ((fd_info)->read_fd)
 
diff --git a/src/core/lib/iomgr/workqueue.h b/src/core/lib/iomgr/workqueue.h
index b2805dc..5b96d1d 100644
--- a/src/core/lib/iomgr/workqueue.h
+++ b/src/core/lib/iomgr/workqueue.h
@@ -40,10 +40,6 @@
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/pollset_set.h"
 
-#ifdef GPR_POSIX_SOCKET
-#include "src/core/lib/iomgr/workqueue_posix.h"
-#endif
-
 #ifdef GPR_WINDOWS
 #include "src/core/lib/iomgr/workqueue_windows.h"
 #endif
@@ -58,20 +54,20 @@
    string will be printed alongside the refcount. When it is not defined, the
    string will be discarded at compilation time. */
 
-//#define GRPC_WORKQUEUE_REFCOUNT_DEBUG
+/*#define GRPC_WORKQUEUE_REFCOUNT_DEBUG*/
 #ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
 #define GRPC_WORKQUEUE_REF(p, r) \
-  (grpc_workqueue_ref((p), __FILE__, __LINE__, (r)), (p))
+  grpc_workqueue_ref((p), __FILE__, __LINE__, (r))
 #define GRPC_WORKQUEUE_UNREF(exec_ctx, p, r) \
   grpc_workqueue_unref((exec_ctx), (p), __FILE__, __LINE__, (r))
-void grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file, int line,
-                        const char *reason);
+grpc_workqueue *grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file,
+                                   int line, const char *reason);
 void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
                           const char *file, int line, const char *reason);
 #else
-#define GRPC_WORKQUEUE_REF(p, r) (grpc_workqueue_ref((p)), (p))
+#define GRPC_WORKQUEUE_REF(p, r) grpc_workqueue_ref((p))
 #define GRPC_WORKQUEUE_UNREF(cl, p, r) grpc_workqueue_unref((cl), (p))
-void grpc_workqueue_ref(grpc_workqueue *workqueue);
+grpc_workqueue *grpc_workqueue_ref(grpc_workqueue *workqueue);
 void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue);
 #endif
 
diff --git a/src/core/lib/iomgr/workqueue_posix.c b/src/core/lib/iomgr/workqueue_posix.c
deleted file mode 100644
index ecfea68..0000000
--- a/src/core/lib/iomgr/workqueue_posix.c
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- *
- * 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.
- *
- */
-
-#include <grpc/support/port_platform.h>
-
-#ifdef GPR_POSIX_SOCKET
-
-#include "src/core/lib/iomgr/workqueue.h"
-
-#include <stdio.h>
-
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
-#include <grpc/support/useful.h>
-
-#include "src/core/lib/iomgr/ev_posix.h"
-#include "src/core/lib/profiling/timers.h"
-
-static void on_readable(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error);
-
-grpc_error *grpc_workqueue_create(grpc_exec_ctx *exec_ctx,
-                                  grpc_workqueue **workqueue) {
-  char name[32];
-  *workqueue = gpr_malloc(sizeof(grpc_workqueue));
-  gpr_ref_init(&(*workqueue)->refs, 1);
-  gpr_atm_no_barrier_store(&(*workqueue)->state, 1);
-  grpc_error *err = grpc_wakeup_fd_init(&(*workqueue)->wakeup_fd);
-  if (err != GRPC_ERROR_NONE) {
-    gpr_free(*workqueue);
-    return err;
-  }
-  sprintf(name, "workqueue:%p", (void *)(*workqueue));
-  (*workqueue)->wakeup_read_fd = grpc_fd_create(
-      GRPC_WAKEUP_FD_GET_READ_FD(&(*workqueue)->wakeup_fd), name);
-  gpr_mpscq_init(&(*workqueue)->queue);
-  grpc_closure_init(&(*workqueue)->read_closure, on_readable, *workqueue);
-  grpc_fd_notify_on_read(exec_ctx, (*workqueue)->wakeup_read_fd,
-                         &(*workqueue)->read_closure);
-  return GRPC_ERROR_NONE;
-}
-
-static void workqueue_destroy(grpc_exec_ctx *exec_ctx,
-                              grpc_workqueue *workqueue) {
-  grpc_fd_shutdown(exec_ctx, workqueue->wakeup_read_fd);
-}
-
-static void workqueue_orphan(grpc_exec_ctx *exec_ctx,
-                             grpc_workqueue *workqueue) {
-  if (gpr_atm_full_fetch_add(&workqueue->state, -1) == 1) {
-    workqueue_destroy(exec_ctx, workqueue);
-  }
-}
-
-#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
-void grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file, int line,
-                        const char *reason) {
-  if (workqueue == NULL) return;
-  gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "WORKQUEUE:%p   ref %d -> %d %s",
-          workqueue, (int)workqueue->refs.count, (int)workqueue->refs.count + 1,
-          reason);
-  gpr_ref(&workqueue->refs);
-}
-#else
-void grpc_workqueue_ref(grpc_workqueue *workqueue) {
-  if (workqueue == NULL) return;
-  gpr_ref(&workqueue->refs);
-}
-#endif
-
-#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
-void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
-                          const char *file, int line, const char *reason) {
-  if (workqueue == NULL) return;
-  gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "WORKQUEUE:%p unref %d -> %d %s",
-          workqueue, (int)workqueue->refs.count, (int)workqueue->refs.count - 1,
-          reason);
-  if (gpr_unref(&workqueue->refs)) {
-    workqueue_orphan(exec_ctx, workqueue);
-  }
-}
-#else
-void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue) {
-  if (workqueue == NULL) return;
-  if (gpr_unref(&workqueue->refs)) {
-    workqueue_orphan(exec_ctx, workqueue);
-  }
-}
-#endif
-
-static void drain(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue) {
-  abort();
-}
-
-static void wakeup(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue) {
-  GPR_TIMER_MARK("workqueue.wakeup", 0);
-  grpc_error *err = grpc_wakeup_fd_wakeup(&workqueue->wakeup_fd);
-  if (!GRPC_LOG_IF_ERROR("wakeupfd_wakeup", err)) {
-    drain(exec_ctx, workqueue);
-  }
-}
-
-static void on_readable(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
-  GPR_TIMER_BEGIN("workqueue.on_readable", 0);
-
-  grpc_workqueue *workqueue = arg;
-
-  if (error != GRPC_ERROR_NONE) {
-    /* HACK: let wakeup_fd code know that we stole the fd */
-    workqueue->wakeup_fd.read_fd = 0;
-    grpc_wakeup_fd_destroy(&workqueue->wakeup_fd);
-    grpc_fd_orphan(exec_ctx, workqueue->wakeup_read_fd, NULL, NULL, "destroy");
-    GPR_ASSERT(gpr_atm_no_barrier_load(&workqueue->state) == 0);
-    gpr_free(workqueue);
-  } else {
-    error = grpc_wakeup_fd_consume_wakeup(&workqueue->wakeup_fd);
-    gpr_mpscq_node *n = gpr_mpscq_pop(&workqueue->queue);
-    if (error == GRPC_ERROR_NONE) {
-      grpc_fd_notify_on_read(exec_ctx, workqueue->wakeup_read_fd,
-                             &workqueue->read_closure);
-    } else {
-      /* recurse to get error handling */
-      on_readable(exec_ctx, arg, error);
-    }
-    if (n == NULL) {
-      /* try again - queue in an inconsistant state */
-      wakeup(exec_ctx, workqueue);
-    } else {
-      switch (gpr_atm_full_fetch_add(&workqueue->state, -2)) {
-        case 3:  // had one count, one unorphaned --> done, unorphaned
-          break;
-        case 2:  // had one count, one orphaned --> done, orphaned
-          workqueue_destroy(exec_ctx, workqueue);
-          break;
-        case 1:
-        case 0:
-          // these values are illegal - representing an already done or
-          // deleted workqueue
-          GPR_UNREACHABLE_CODE(break);
-        default:
-          // schedule a wakeup since there's more to do
-          wakeup(exec_ctx, workqueue);
-      }
-      grpc_closure *cl = (grpc_closure *)n;
-      grpc_error *clerr = cl->error;
-      cl->cb(exec_ctx, cl->cb_arg, clerr);
-      GRPC_ERROR_UNREF(clerr);
-    }
-  }
-
-  GPR_TIMER_END("workqueue.on_readable", 0);
-}
-
-void grpc_workqueue_enqueue(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
-                            grpc_closure *closure, grpc_error *error) {
-  GPR_TIMER_BEGIN("workqueue.enqueue", 0);
-  gpr_atm last = gpr_atm_full_fetch_add(&workqueue->state, 2);
-  GPR_ASSERT(last & 1);
-  closure->error = error;
-  gpr_mpscq_push(&workqueue->queue, &closure->next_data.atm_next);
-  if (last == 1) {
-    wakeup(exec_ctx, workqueue);
-  }
-  GPR_TIMER_END("workqueue.enqueue", 0);
-}
-
-#endif /* GPR_POSIX_SOCKET */
diff --git a/src/core/lib/iomgr/workqueue_windows.c b/src/core/lib/iomgr/workqueue_windows.c
index ee81dc2..5c93d3c 100644
--- a/src/core/lib/iomgr/workqueue_windows.c
+++ b/src/core/lib/iomgr/workqueue_windows.c
@@ -43,12 +43,16 @@
 // workqueues.
 
 #ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG
-void grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file, int line,
-                        const char *reason) {}
+grpc_workqueue *grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file,
+                                   int line, const char *reason) {
+  return workqueue;
+}
 void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue,
                           const char *file, int line, const char *reason) {}
 #else
-void grpc_workqueue_ref(grpc_workqueue *workqueue) {}
+grpc_workqueue *grpc_workqueue_ref(grpc_workqueue *workqueue) {
+  return workqueue;
+}
 void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue) {}
 #endif
 
diff --git a/src/core/lib/profiling/basic_timers.c b/src/core/lib/profiling/basic_timers.c
index 51813d0..bdf9af2 100644
--- a/src/core/lib/profiling/basic_timers.c
+++ b/src/core/lib/profiling/basic_timers.c
@@ -83,6 +83,7 @@
 static gpr_thd_id g_writing_thread;
 static __thread int g_thread_id;
 static int g_next_thread_id;
+static int g_writing_enabled = 1;
 
 static int timer_log_push_back(gpr_timer_log_list *list, gpr_timer_log *log) {
   if (list->head == NULL) {
@@ -177,7 +178,7 @@
   }
 }
 
-static void finish_writing() {
+static void finish_writing(void) {
   pthread_mutex_lock(&g_mu);
   g_shutdown = 1;
   pthread_cond_signal(&g_cv);
@@ -230,6 +231,10 @@
                                int important, const char *file, int line) {
   gpr_timer_entry *entry;
 
+  if (!g_writing_enabled) {
+    return;
+  }
+
   if (g_thread_log == NULL || g_thread_log->num_entries == MAX_COUNT) {
     rotate_log();
   }
@@ -261,6 +266,8 @@
   gpr_timers_log_add(tagstr, END, important, file, line);
 }
 
+void gpr_timer_set_enabled(int enabled) { g_writing_enabled = enabled; }
+
 /* Basic profiler specific API functions. */
 void gpr_timers_global_init(void) {}
 
@@ -272,4 +279,6 @@
 void gpr_timers_global_destroy(void) {}
 
 void gpr_timers_set_log_filename(const char *filename) {}
+
+void gpr_timer_set_enabled(int enabled) {}
 #endif /* GRPC_BASIC_PROFILER */
diff --git a/src/core/lib/profiling/timers.h b/src/core/lib/profiling/timers.h
index c8567e8..621cdbf 100644
--- a/src/core/lib/profiling/timers.h
+++ b/src/core/lib/profiling/timers.h
@@ -50,6 +50,8 @@
 
 void gpr_timers_set_log_filename(const char *filename);
 
+void gpr_timer_set_enabled(int enabled);
+
 #if !(defined(GRPC_STAP_PROFILER) + defined(GRPC_BASIC_PROFILER))
 /* No profiling. No-op all the things. */
 #define GPR_TIMER_MARK(tag, important) \
diff --git a/src/core/lib/support/log.c b/src/core/lib/support/log.c
index 899f121..af1651d 100644
--- a/src/core/lib/support/log.c
+++ b/src/core/lib/support/log.c
@@ -60,8 +60,9 @@
 
 void gpr_log_message(const char *file, int line, gpr_log_severity severity,
                      const char *message) {
-  if ((gpr_atm)severity < gpr_atm_no_barrier_load(&g_min_severity_to_print))
+  if ((gpr_atm)severity < gpr_atm_no_barrier_load(&g_min_severity_to_print)) {
     return;
+  }
 
   gpr_log_func_args lfargs;
   memset(&lfargs, 0, sizeof(lfargs));
@@ -82,11 +83,11 @@
 
   gpr_atm min_severity_to_print = GPR_LOG_SEVERITY_ERROR;
   if (verbosity != NULL) {
-    if (strcmp(verbosity, "DEBUG") == 0) {
+    if (gpr_stricmp(verbosity, "DEBUG") == 0) {
       min_severity_to_print = (gpr_atm)GPR_LOG_SEVERITY_DEBUG;
-    } else if (strcmp(verbosity, "INFO") == 0) {
+    } else if (gpr_stricmp(verbosity, "INFO") == 0) {
       min_severity_to_print = (gpr_atm)GPR_LOG_SEVERITY_INFO;
-    } else if (strcmp(verbosity, "ERROR") == 0) {
+    } else if (gpr_stricmp(verbosity, "ERROR") == 0) {
       min_severity_to_print = (gpr_atm)GPR_LOG_SEVERITY_ERROR;
     }
     gpr_free(verbosity);
diff --git a/src/core/lib/support/string.c b/src/core/lib/support/string.c
index 30c1e67..d17fb9d 100644
--- a/src/core/lib/support/string.c
+++ b/src/core/lib/support/string.c
@@ -304,3 +304,14 @@
 char *gpr_strvec_flatten(gpr_strvec *sv, size_t *final_length) {
   return gpr_strjoin((const char **)sv->strs, sv->count, final_length);
 }
+
+int gpr_stricmp(const char *a, const char *b) {
+  int ca, cb;
+  do {
+    ca = tolower(*a);
+    cb = tolower(*b);
+    ++a;
+    ++b;
+  } while (ca == cb && ca && cb);
+  return ca - cb;
+}
diff --git a/src/core/lib/support/string.h b/src/core/lib/support/string.h
index 2b6bb3e..9a94e94 100644
--- a/src/core/lib/support/string.h
+++ b/src/core/lib/support/string.h
@@ -118,6 +118,10 @@
    total_length as per gpr_strjoin */
 char *gpr_strvec_flatten(gpr_strvec *strs, size_t *total_length);
 
+/** Case insensitive string comparison... return <0 if lower(a)<lower(b), ==0 if
+    lower(a)==lower(b), >0 if lower(a)>lower(b) */
+int gpr_stricmp(const char *a, const char *b);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/core/lib/surface/call.c b/src/core/lib/surface/call.c
index b0f66f4..c4effa9 100644
--- a/src/core/lib/surface/call.c
+++ b/src/core/lib/surface/call.c
@@ -223,33 +223,33 @@
 static void receiving_slice_ready(grpc_exec_ctx *exec_ctx, void *bctlp,
                                   grpc_error *error);
 
-grpc_call *grpc_call_create(
-    grpc_channel *channel, grpc_call *parent_call, uint32_t propagation_mask,
-    grpc_completion_queue *cq, grpc_pollset_set *pollset_set_alternative,
-    const void *server_transport_data, grpc_mdelem **add_initial_metadata,
-    size_t add_initial_metadata_count, gpr_timespec send_deadline) {
+grpc_error *grpc_call_create(const grpc_call_create_args *args,
+                             grpc_call **out_call) {
   size_t i, j;
-  grpc_channel_stack *channel_stack = grpc_channel_get_channel_stack(channel);
+  grpc_channel_stack *channel_stack =
+      grpc_channel_get_channel_stack(args->channel);
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
   grpc_call *call;
   GPR_TIMER_BEGIN("grpc_call_create", 0);
   call = gpr_malloc(sizeof(grpc_call) + channel_stack->call_stack_size);
+  *out_call = call;
   memset(call, 0, sizeof(grpc_call));
   gpr_mu_init(&call->mu);
-  call->channel = channel;
-  call->cq = cq;
-  call->parent = parent_call;
+  call->channel = args->channel;
+  call->cq = args->cq;
+  call->parent = args->parent_call;
   /* Always support no compression */
   GPR_BITSET(&call->encodings_accepted_by_peer, GRPC_COMPRESS_NONE);
-  call->is_client = server_transport_data == NULL;
+  call->is_client = args->server_transport_data == NULL;
   if (call->is_client) {
-    GPR_ASSERT(add_initial_metadata_count < MAX_SEND_EXTRA_METADATA_COUNT);
-    for (i = 0; i < add_initial_metadata_count; i++) {
-      call->send_extra_metadata[i].md = add_initial_metadata[i];
+    GPR_ASSERT(args->add_initial_metadata_count <
+               MAX_SEND_EXTRA_METADATA_COUNT);
+    for (i = 0; i < args->add_initial_metadata_count; i++) {
+      call->send_extra_metadata[i].md = args->add_initial_metadata[i];
     }
-    call->send_extra_metadata_count = (int)add_initial_metadata_count;
+    call->send_extra_metadata_count = (int)args->add_initial_metadata_count;
   } else {
-    GPR_ASSERT(add_initial_metadata_count == 0);
+    GPR_ASSERT(args->add_initial_metadata_count == 0);
     call->send_extra_metadata_count = 0;
   }
   for (i = 0; i < 2; i++) {
@@ -257,75 +257,75 @@
       call->metadata_batch[i][j].deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
     }
   }
-  send_deadline = gpr_convert_clock_type(send_deadline, GPR_CLOCK_MONOTONIC);
+  gpr_timespec send_deadline =
+      gpr_convert_clock_type(args->send_deadline, GPR_CLOCK_MONOTONIC);
 
-  if (parent_call != NULL) {
-    GRPC_CALL_INTERNAL_REF(parent_call, "child");
+  if (args->parent_call != NULL) {
+    GRPC_CALL_INTERNAL_REF(args->parent_call, "child");
     GPR_ASSERT(call->is_client);
-    GPR_ASSERT(!parent_call->is_client);
+    GPR_ASSERT(!args->parent_call->is_client);
 
-    gpr_mu_lock(&parent_call->mu);
+    gpr_mu_lock(&args->parent_call->mu);
 
-    if (propagation_mask & GRPC_PROPAGATE_DEADLINE) {
+    if (args->propagation_mask & GRPC_PROPAGATE_DEADLINE) {
       send_deadline = gpr_time_min(
           gpr_convert_clock_type(send_deadline,
-                                 parent_call->send_deadline.clock_type),
-          parent_call->send_deadline);
+                                 args->parent_call->send_deadline.clock_type),
+          args->parent_call->send_deadline);
     }
     /* for now GRPC_PROPAGATE_TRACING_CONTEXT *MUST* be passed with
      * GRPC_PROPAGATE_STATS_CONTEXT */
     /* TODO(ctiller): This should change to use the appropriate census start_op
      * call. */
-    if (propagation_mask & GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT) {
-      GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT);
-      grpc_call_context_set(call, GRPC_CONTEXT_TRACING,
-                            parent_call->context[GRPC_CONTEXT_TRACING].value,
-                            NULL);
+    if (args->propagation_mask & GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT) {
+      GPR_ASSERT(args->propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT);
+      grpc_call_context_set(
+          call, GRPC_CONTEXT_TRACING,
+          args->parent_call->context[GRPC_CONTEXT_TRACING].value, NULL);
     } else {
-      GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT);
+      GPR_ASSERT(args->propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT);
     }
-    if (propagation_mask & GRPC_PROPAGATE_CANCELLATION) {
+    if (args->propagation_mask & GRPC_PROPAGATE_CANCELLATION) {
       call->cancellation_is_inherited = 1;
     }
 
-    if (parent_call->first_child == NULL) {
-      parent_call->first_child = call;
+    if (args->parent_call->first_child == NULL) {
+      args->parent_call->first_child = call;
       call->sibling_next = call->sibling_prev = call;
     } else {
-      call->sibling_next = parent_call->first_child;
-      call->sibling_prev = parent_call->first_child->sibling_prev;
+      call->sibling_next = args->parent_call->first_child;
+      call->sibling_prev = args->parent_call->first_child->sibling_prev;
       call->sibling_next->sibling_prev = call->sibling_prev->sibling_next =
           call;
     }
 
-    gpr_mu_unlock(&parent_call->mu);
+    gpr_mu_unlock(&args->parent_call->mu);
   }
 
   call->send_deadline = send_deadline;
 
-  GRPC_CHANNEL_INTERNAL_REF(channel, "call");
+  GRPC_CHANNEL_INTERNAL_REF(args->channel, "call");
   /* initial refcount dropped by grpc_call_destroy */
   grpc_error *error = grpc_call_stack_init(
       &exec_ctx, channel_stack, 1, destroy_call, call, call->context,
-      server_transport_data, send_deadline, CALL_STACK_FROM_CALL(call));
+      args->server_transport_data, send_deadline, CALL_STACK_FROM_CALL(call));
   if (error != GRPC_ERROR_NONE) {
     grpc_status_code status;
     const char *error_str;
     grpc_error_get_status(error, &status, &error_str);
     close_with_status(&exec_ctx, call, status, error_str);
-    GRPC_ERROR_UNREF(error);
   }
-  if (cq != NULL) {
+  if (args->cq != NULL) {
     GPR_ASSERT(
-        pollset_set_alternative == NULL &&
+        args->pollset_set_alternative == NULL &&
         "Only one of 'cq' and 'pollset_set_alternative' should be non-NULL.");
-    GRPC_CQ_INTERNAL_REF(cq, "bind");
+    GRPC_CQ_INTERNAL_REF(args->cq, "bind");
     call->pollent =
-        grpc_polling_entity_create_from_pollset(grpc_cq_pollset(cq));
+        grpc_polling_entity_create_from_pollset(grpc_cq_pollset(args->cq));
   }
-  if (pollset_set_alternative != NULL) {
-    call->pollent =
-        grpc_polling_entity_create_from_pollset_set(pollset_set_alternative);
+  if (args->pollset_set_alternative != NULL) {
+    call->pollent = grpc_polling_entity_create_from_pollset_set(
+        args->pollset_set_alternative);
   }
   if (!grpc_polling_entity_is_empty(&call->pollent)) {
     grpc_call_stack_set_pollset_or_pollset_set(
@@ -334,7 +334,7 @@
 
   grpc_exec_ctx_finish(&exec_ctx);
   GPR_TIMER_END("grpc_call_create", 0);
-  return call;
+  return error;
 }
 
 void grpc_call_set_completion_queue(grpc_exec_ctx *exec_ctx, grpc_call *call,
@@ -1038,9 +1038,14 @@
 static void post_batch_completion(grpc_exec_ctx *exec_ctx,
                                   batch_control *bctl) {
   grpc_call *call = bctl->call;
+  grpc_error *error = bctl->error;
+  if (bctl->recv_final_op) {
+    GRPC_ERROR_UNREF(error);
+    error = GRPC_ERROR_NONE;
+  }
   if (bctl->is_notify_tag_closure) {
     /* unrefs bctl->error */
-    grpc_exec_ctx_sched(exec_ctx, bctl->notify_tag, bctl->error, NULL);
+    grpc_closure_run(exec_ctx, bctl->notify_tag, error);
     gpr_mu_lock(&call->mu);
     bctl->call->used_batches =
         (uint8_t)(bctl->call->used_batches &
@@ -1049,7 +1054,7 @@
     GRPC_CALL_INTERNAL_UNREF(exec_ctx, call, "completion");
   } else {
     /* unrefs bctl->error */
-    grpc_cq_end_op(exec_ctx, bctl->call->cq, bctl->notify_tag, bctl->error,
+    grpc_cq_end_op(exec_ctx, bctl->call->cq, bctl->notify_tag, error,
                    finish_batch_completion, bctl, &bctl->cq_completion);
   }
 }
@@ -1198,6 +1203,14 @@
   }
 }
 
+static void add_batch_error(batch_control *bctl, grpc_error *error) {
+  if (error == GRPC_ERROR_NONE) return;
+  if (bctl->error == GRPC_ERROR_NONE) {
+    bctl->error = GRPC_ERROR_CREATE("Call batch operation failed");
+  }
+  bctl->error = grpc_error_add_child(bctl->error, error);
+}
+
 static void receiving_initial_metadata_ready(grpc_exec_ctx *exec_ctx,
                                              void *bctlp, grpc_error *error) {
   batch_control *bctl = bctlp;
@@ -1205,9 +1218,8 @@
 
   gpr_mu_lock(&call->mu);
 
-  if (error != GRPC_ERROR_NONE) {
-    bctl->error = GRPC_ERROR_REF(error);
-  } else {
+  add_batch_error(bctl, GRPC_ERROR_REF(error));
+  if (error == GRPC_ERROR_NONE) {
     grpc_metadata_batch *md =
         &call->metadata_batch[1 /* is_receiving */][0 /* is_trailing */];
     grpc_metadata_batch_filter(md, recv_initial_filter, call);
@@ -1304,8 +1316,7 @@
     GRPC_ERROR_UNREF(error);
     error = GRPC_ERROR_NONE;
   }
-  GRPC_ERROR_UNREF(bctl->error);
-  bctl->error = GRPC_ERROR_REF(error);
+  add_batch_error(bctl, GRPC_ERROR_REF(error));
   gpr_mu_unlock(&call->mu);
   if (gpr_unref(&bctl->steps_to_complete)) {
     post_batch_completion(exec_ctx, bctl);
@@ -1341,6 +1352,7 @@
 
   grpc_transport_stream_op *stream_op = &bctl->op;
   memset(stream_op, 0, sizeof(*stream_op));
+  stream_op->covered_by_poller = true;
 
   if (nops == 0) {
     GRPC_CALL_INTERNAL_REF(call, "completion");
diff --git a/src/core/lib/surface/call.h b/src/core/lib/surface/call.h
index 3a78fe3..18af41b 100644
--- a/src/core/lib/surface/call.h
+++ b/src/core/lib/surface/call.h
@@ -49,15 +49,29 @@
                                            grpc_call *call, int success,
                                            void *user_data);
 
-grpc_call *grpc_call_create(grpc_channel *channel, grpc_call *parent_call,
-                            uint32_t propagation_mask,
-                            grpc_completion_queue *cq,
-                            /* if not NULL, it'll be used in lieu of \a cq */
-                            grpc_pollset_set *pollset_set_alternative,
-                            const void *server_transport_data,
-                            grpc_mdelem **add_initial_metadata,
-                            size_t add_initial_metadata_count,
-                            gpr_timespec send_deadline);
+typedef struct grpc_call_create_args {
+  grpc_channel *channel;
+
+  grpc_call *parent_call;
+  uint32_t propagation_mask;
+
+  grpc_completion_queue *cq;
+  /* if not NULL, it'll be used in lieu of cq */
+  grpc_pollset_set *pollset_set_alternative;
+
+  const void *server_transport_data;
+
+  grpc_mdelem **add_initial_metadata;
+  size_t add_initial_metadata_count;
+
+  gpr_timespec send_deadline;
+} grpc_call_create_args;
+
+/* Create a new call based on \a args.
+   Regardless of success or failure, always returns a valid new call into *call
+   */
+grpc_error *grpc_call_create(const grpc_call_create_args *args,
+                             grpc_call **call);
 
 void grpc_call_set_completion_queue(grpc_exec_ctx *exec_ctx, grpc_call *call,
                                     grpc_completion_queue *cq);
diff --git a/src/core/lib/surface/channel.c b/src/core/lib/surface/channel.c
index 6adb70a..92d783b 100644
--- a/src/core/lib/surface/channel.c
+++ b/src/core/lib/surface/channel.c
@@ -193,9 +193,21 @@
     send_metadata[num_metadata++] = GRPC_MDELEM_REF(channel->default_authority);
   }
 
-  return grpc_call_create(channel, parent_call, propagation_mask, cq,
-                          pollset_set_alternative, NULL, send_metadata,
-                          num_metadata, deadline);
+  grpc_call_create_args args;
+  memset(&args, 0, sizeof(args));
+  args.channel = channel;
+  args.parent_call = parent_call;
+  args.propagation_mask = propagation_mask;
+  args.cq = cq;
+  args.pollset_set_alternative = pollset_set_alternative;
+  args.server_transport_data = NULL;
+  args.add_initial_metadata = send_metadata;
+  args.add_initial_metadata_count = num_metadata;
+  args.send_deadline = deadline;
+
+  grpc_call *call;
+  GRPC_LOG_IF_ERROR("call_create", grpc_call_create(&args, &call));
+  return call;
 }
 
 grpc_call *grpc_channel_create_call(grpc_channel *channel,
diff --git a/src/core/lib/surface/completion_queue.c b/src/core/lib/surface/completion_queue.c
index 5978884..4e0feb5 100644
--- a/src/core/lib/surface/completion_queue.c
+++ b/src/core/lib/surface/completion_queue.c
@@ -39,6 +39,7 @@
 #include <grpc/support/alloc.h>
 #include <grpc/support/atm.h>
 #include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
 #include <grpc/support/time.h>
 
 #include "src/core/lib/iomgr/pollset.h"
@@ -50,6 +51,9 @@
 #include "src/core/lib/surface/event_string.h"
 
 int grpc_trace_operation_failures;
+#ifndef NDEBUG
+int grpc_trace_pending_tags;
+#endif
 
 typedef struct {
   grpc_pollset_worker **worker;
@@ -67,6 +71,9 @@
   gpr_refcount pending_events;
   /** Once owning_refs drops to zero, we will destroy the cq */
   gpr_refcount owning_refs;
+  /** counter of how many things have ever been queued on this completion queue
+      useful for avoiding locks to check the queue */
+  gpr_atm things_queued_ever;
   /** 0 initially, 1 once we've begun shutting down */
   int shutdown;
   int shutdown_called;
@@ -121,15 +128,6 @@
   }
 }
 
-struct grpc_cq_alarm {
-  grpc_timer alarm;
-  grpc_cq_completion completion;
-  /** completion queue where events about this alarm will be posted */
-  grpc_completion_queue *cq;
-  /** user supplied tag */
-  void *tag;
-};
-
 grpc_completion_queue *grpc_completion_queue_create(void *reserved) {
   grpc_completion_queue *cc;
   GPR_ASSERT(!reserved);
@@ -166,6 +164,7 @@
   cc->is_server_cq = 0;
   cc->is_non_listening_server_cq = 0;
   cc->num_pluckers = 0;
+  gpr_atm_no_barrier_store(&cc->things_queued_ever, 0);
 #ifndef NDEBUG
   cc->outstanding_tag_count = 0;
 #endif
@@ -276,6 +275,7 @@
   GPR_ASSERT(found);
 #endif
   shutdown = gpr_unref(&cc->pending_events);
+  gpr_atm_no_barrier_fetch_add(&cc->things_queued_ever, 1);
   if (!shutdown) {
     cc->completed_tail->next =
         ((uintptr_t)storage) | (1u & (uintptr_t)cc->completed_tail->next);
@@ -313,13 +313,66 @@
   GRPC_ERROR_UNREF(error);
 }
 
+typedef struct {
+  gpr_atm last_seen_things_queued_ever;
+  grpc_completion_queue *cq;
+  gpr_timespec deadline;
+  grpc_cq_completion *stolen_completion;
+  void *tag; /* for pluck */
+  bool first_loop;
+} cq_is_finished_arg;
+
+static bool cq_is_next_finished(grpc_exec_ctx *exec_ctx, void *arg) {
+  cq_is_finished_arg *a = arg;
+  grpc_completion_queue *cq = a->cq;
+  GPR_ASSERT(a->stolen_completion == NULL);
+  gpr_atm current_last_seen_things_queued_ever =
+      gpr_atm_no_barrier_load(&cq->things_queued_ever);
+  if (current_last_seen_things_queued_ever != a->last_seen_things_queued_ever) {
+    gpr_mu_lock(cq->mu);
+    a->last_seen_things_queued_ever =
+        gpr_atm_no_barrier_load(&cq->things_queued_ever);
+    if (cq->completed_tail != &cq->completed_head) {
+      a->stolen_completion = (grpc_cq_completion *)cq->completed_head.next;
+      cq->completed_head.next = a->stolen_completion->next & ~(uintptr_t)1;
+      if (a->stolen_completion == cq->completed_tail) {
+        cq->completed_tail = &cq->completed_head;
+      }
+      gpr_mu_unlock(cq->mu);
+      return true;
+    }
+    gpr_mu_unlock(cq->mu);
+  }
+  return !a->first_loop &&
+         gpr_time_cmp(a->deadline, gpr_now(a->deadline.clock_type)) < 0;
+}
+
+#ifndef NDEBUG
+static void dump_pending_tags(grpc_completion_queue *cc) {
+  if (!grpc_trace_pending_tags) return;
+
+  gpr_strvec v;
+  gpr_strvec_init(&v);
+  gpr_strvec_add(&v, gpr_strdup("PENDING TAGS:"));
+  for (size_t i = 0; i < cc->outstanding_tag_count; i++) {
+    char *s;
+    gpr_asprintf(&s, " %p", cc->outstanding_tags[i]);
+    gpr_strvec_add(&v, s);
+  }
+  char *out = gpr_strvec_flatten(&v, NULL);
+  gpr_strvec_destroy(&v);
+  gpr_log(GPR_DEBUG, "%s", out);
+  gpr_free(out);
+}
+#else
+static void dump_pending_tags(grpc_completion_queue *cc) {}
+#endif
+
 grpc_event grpc_completion_queue_next(grpc_completion_queue *cc,
                                       gpr_timespec deadline, void *reserved) {
   grpc_event ret;
   grpc_pollset_worker *worker = NULL;
-  int first_loop = 1;
   gpr_timespec now;
-  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
 
   GPR_TIMER_BEGIN("grpc_completion_queue_next", 0);
 
@@ -333,11 +386,33 @@
           reserved));
   GPR_ASSERT(!reserved);
 
+  dump_pending_tags(cc);
+
   deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC);
 
   GRPC_CQ_INTERNAL_REF(cc, "next");
   gpr_mu_lock(cc->mu);
+  cq_is_finished_arg is_finished_arg = {
+      .last_seen_things_queued_ever =
+          gpr_atm_no_barrier_load(&cc->things_queued_ever),
+      .cq = cc,
+      .deadline = deadline,
+      .stolen_completion = NULL,
+      .tag = NULL,
+      .first_loop = true};
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT_WITH_FINISH_CHECK(
+      cq_is_next_finished, &is_finished_arg);
   for (;;) {
+    if (is_finished_arg.stolen_completion != NULL) {
+      gpr_mu_unlock(cc->mu);
+      grpc_cq_completion *c = is_finished_arg.stolen_completion;
+      is_finished_arg.stolen_completion = NULL;
+      ret.type = GRPC_OP_COMPLETE;
+      ret.success = c->next & 1u;
+      ret.tag = c->tag;
+      c->done(&exec_ctx, c->done_arg, c);
+      break;
+    }
     if (cc->completed_tail != &cc->completed_head) {
       grpc_cq_completion *c = (grpc_cq_completion *)cc->completed_head.next;
       cc->completed_head.next = c->next & ~(uintptr_t)1;
@@ -358,13 +433,13 @@
       break;
     }
     now = gpr_now(GPR_CLOCK_MONOTONIC);
-    if (!first_loop && gpr_time_cmp(now, deadline) >= 0) {
+    if (!is_finished_arg.first_loop && gpr_time_cmp(now, deadline) >= 0) {
       gpr_mu_unlock(cc->mu);
       memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_TIMEOUT;
+      dump_pending_tags(cc);
       break;
     }
-    first_loop = 0;
     /* Check alarms - these are a global resource so we just ping
        each time through on every pollset.
        May update deadline to ensure timely wakeups.
@@ -387,13 +462,16 @@
         GRPC_ERROR_UNREF(err);
         memset(&ret, 0, sizeof(ret));
         ret.type = GRPC_QUEUE_TIMEOUT;
+        dump_pending_tags(cc);
         break;
       }
     }
+    is_finished_arg.first_loop = false;
   }
   GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ret);
   GRPC_CQ_INTERNAL_UNREF(cc, "next");
   grpc_exec_ctx_finish(&exec_ctx);
+  GPR_ASSERT(is_finished_arg.stolen_completion == NULL);
 
   GPR_TIMER_END("grpc_completion_queue_next", 0);
 
@@ -424,6 +502,37 @@
   GPR_UNREACHABLE_CODE(return );
 }
 
+static bool cq_is_pluck_finished(grpc_exec_ctx *exec_ctx, void *arg) {
+  cq_is_finished_arg *a = arg;
+  grpc_completion_queue *cq = a->cq;
+  GPR_ASSERT(a->stolen_completion == NULL);
+  gpr_atm current_last_seen_things_queued_ever =
+      gpr_atm_no_barrier_load(&cq->things_queued_ever);
+  if (current_last_seen_things_queued_ever != a->last_seen_things_queued_ever) {
+    gpr_mu_lock(cq->mu);
+    a->last_seen_things_queued_ever =
+        gpr_atm_no_barrier_load(&cq->things_queued_ever);
+    grpc_cq_completion *c;
+    grpc_cq_completion *prev = &cq->completed_head;
+    while ((c = (grpc_cq_completion *)(prev->next & ~(uintptr_t)1)) !=
+           &cq->completed_head) {
+      if (c->tag == a->tag) {
+        prev->next = (prev->next & (uintptr_t)1) | (c->next & ~(uintptr_t)1);
+        if (c == cq->completed_tail) {
+          cq->completed_tail = prev;
+        }
+        gpr_mu_unlock(cq->mu);
+        a->stolen_completion = c;
+        return true;
+      }
+      prev = c;
+    }
+    gpr_mu_unlock(cq->mu);
+  }
+  return !a->first_loop &&
+         gpr_time_cmp(a->deadline, gpr_now(a->deadline.clock_type)) < 0;
+}
+
 grpc_event grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag,
                                        gpr_timespec deadline, void *reserved) {
   grpc_event ret;
@@ -431,8 +540,6 @@
   grpc_cq_completion *prev;
   grpc_pollset_worker *worker = NULL;
   gpr_timespec now;
-  int first_loop = 1;
-  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
 
   GPR_TIMER_BEGIN("grpc_completion_queue_pluck", 0);
 
@@ -448,11 +555,33 @@
   }
   GPR_ASSERT(!reserved);
 
+  dump_pending_tags(cc);
+
   deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC);
 
   GRPC_CQ_INTERNAL_REF(cc, "pluck");
   gpr_mu_lock(cc->mu);
+  cq_is_finished_arg is_finished_arg = {
+      .last_seen_things_queued_ever =
+          gpr_atm_no_barrier_load(&cc->things_queued_ever),
+      .cq = cc,
+      .deadline = deadline,
+      .stolen_completion = NULL,
+      .tag = tag,
+      .first_loop = true};
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT_WITH_FINISH_CHECK(
+      cq_is_pluck_finished, &is_finished_arg);
   for (;;) {
+    if (is_finished_arg.stolen_completion != NULL) {
+      gpr_mu_unlock(cc->mu);
+      c = is_finished_arg.stolen_completion;
+      is_finished_arg.stolen_completion = NULL;
+      ret.type = GRPC_OP_COMPLETE;
+      ret.success = c->next & 1u;
+      ret.tag = c->tag;
+      c->done(&exec_ctx, c->done_arg, c);
+      break;
+    }
     prev = &cc->completed_head;
     while ((c = (grpc_cq_completion *)(prev->next & ~(uintptr_t)1)) !=
            &cc->completed_head) {
@@ -485,17 +614,18 @@
       memset(&ret, 0, sizeof(ret));
       /* TODO(ctiller): should we use a different result here */
       ret.type = GRPC_QUEUE_TIMEOUT;
+      dump_pending_tags(cc);
       break;
     }
     now = gpr_now(GPR_CLOCK_MONOTONIC);
-    if (!first_loop && gpr_time_cmp(now, deadline) >= 0) {
+    if (!is_finished_arg.first_loop && gpr_time_cmp(now, deadline) >= 0) {
       del_plucker(cc, tag, &worker);
       gpr_mu_unlock(cc->mu);
       memset(&ret, 0, sizeof(ret));
       ret.type = GRPC_QUEUE_TIMEOUT;
+      dump_pending_tags(cc);
       break;
     }
-    first_loop = 0;
     /* Check alarms - these are a global resource so we just ping
        each time through on every pollset.
        May update deadline to ensure timely wakeups.
@@ -518,15 +648,18 @@
         GRPC_ERROR_UNREF(err);
         memset(&ret, 0, sizeof(ret));
         ret.type = GRPC_QUEUE_TIMEOUT;
+        dump_pending_tags(cc);
         break;
       }
     }
+    is_finished_arg.first_loop = false;
     del_plucker(cc, tag, &worker);
   }
 done:
   GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ret);
   GRPC_CQ_INTERNAL_UNREF(cc, "pluck");
   grpc_exec_ctx_finish(&exec_ctx);
+  GPR_ASSERT(is_finished_arg.stolen_completion == NULL);
 
   GPR_TIMER_END("grpc_completion_queue_pluck", 0);
 
diff --git a/src/core/lib/surface/completion_queue.h b/src/core/lib/surface/completion_queue.h
index 4dbf3aa..c1cafba 100644
--- a/src/core/lib/surface/completion_queue.h
+++ b/src/core/lib/surface/completion_queue.h
@@ -44,6 +44,9 @@
 extern int grpc_cq_pluck_trace;
 extern int grpc_cq_event_timeout_trace;
 extern int grpc_trace_operation_failures;
+#ifndef NDEBUG
+extern int grpc_trace_pending_tags;
+#endif
 
 typedef struct grpc_cq_completion {
   /** user supplied tag */
diff --git a/src/core/lib/surface/init.c b/src/core/lib/surface/init.c
index 289f4ce..8ca0643 100644
--- a/src/core/lib/surface/init.c
+++ b/src/core/lib/surface/init.c
@@ -184,12 +184,16 @@
     grpc_register_tracer("compression", &grpc_compression_trace);
     grpc_register_tracer("queue_pluck", &grpc_cq_pluck_trace);
     grpc_register_tracer("combiner", &grpc_combiner_trace);
+    grpc_register_tracer("server_channel", &grpc_server_channel_trace);
     // Default pluck trace to 1
     grpc_cq_pluck_trace = 1;
     grpc_register_tracer("queue_timeout", &grpc_cq_event_timeout_trace);
     // Default timeout trace to 1
     grpc_cq_event_timeout_trace = 1;
     grpc_register_tracer("op_failure", &grpc_trace_operation_failures);
+#ifndef NDEBUG
+    grpc_register_tracer("pending_tags", &grpc_trace_pending_tags);
+#endif
     grpc_security_pre_init();
     grpc_iomgr_init();
     grpc_executor_init();
diff --git a/src/core/lib/surface/server.c b/src/core/lib/surface/server.c
index 7300d79..3a90308 100644
--- a/src/core/lib/surface/server.c
+++ b/src/core/lib/surface/server.c
@@ -71,6 +71,8 @@
 
 typedef enum { BATCH_CALL, REGISTERED_CALL } requested_call_type;
 
+int grpc_server_channel_trace = 0;
+
 typedef struct requested_call {
   requested_call_type type;
   size_t cq_idx;
@@ -280,6 +282,7 @@
   grpc_channel_element *elem;
 
   op->send_goaway = send_goaway;
+  op->set_accept_stream = true;
   sc->slice = gpr_slice_from_copied_string("Server shutdown");
   op->goaway_message = &sc->slice;
   op->goaway_status = GRPC_STATUS_OK;
@@ -439,6 +442,13 @@
   chand->finish_destroy_channel_closure.cb = finish_destroy_channel;
   chand->finish_destroy_channel_closure.cb_arg = chand;
 
+  if (grpc_server_channel_trace && error != GRPC_ERROR_NONE) {
+    const char *msg = grpc_error_string(error);
+    gpr_log(GPR_INFO, "Disconnected client: %s", msg);
+    grpc_error_free_string(msg);
+  }
+  GRPC_ERROR_UNREF(error);
+
   grpc_transport_op *op =
       grpc_make_transport_op(&chand->finish_destroy_channel_closure);
   op->set_accept_stream = true;
@@ -446,13 +456,6 @@
                        grpc_channel_stack_element(
                            grpc_channel_get_channel_stack(chand->channel), 0),
                        op);
-
-  if (error != GRPC_ERROR_NONE) {
-    const char *msg = grpc_error_string(error);
-    gpr_log(GPR_INFO, "Disconnected client: %s", msg);
-    grpc_error_free_string(msg);
-  }
-  GRPC_ERROR_UNREF(error);
 }
 
 static void cpstr(char **dest, size_t *capacity, grpc_mdstr *value) {
@@ -773,8 +776,7 @@
         GRPC_ERROR_CREATE_REFERENCING("Missing :authority or :path", &error, 1);
   }
 
-  grpc_exec_ctx_sched(exec_ctx, calld->on_done_recv_initial_metadata, error,
-                      NULL);
+  grpc_closure_run(exec_ctx, calld->on_done_recv_initial_metadata, error);
 }
 
 static void server_mutate_op(grpc_call_element *elem,
@@ -829,11 +831,20 @@
                           const void *transport_server_data) {
   channel_data *chand = cd;
   /* create a call */
-  grpc_call *call = grpc_call_create(chand->channel, NULL, 0, NULL, NULL,
-                                     transport_server_data, NULL, 0,
-                                     gpr_inf_future(GPR_CLOCK_MONOTONIC));
+  grpc_call_create_args args;
+  memset(&args, 0, sizeof(args));
+  args.channel = chand->channel;
+  args.server_transport_data = transport_server_data;
+  args.send_deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
+  grpc_call *call;
+  grpc_error *error = grpc_call_create(&args, &call);
   grpc_call_element *elem =
       grpc_call_stack_element(grpc_call_get_call_stack(call), 0);
+  if (error != GRPC_ERROR_NONE) {
+    got_initial_metadata(exec_ctx, elem, error);
+    GRPC_ERROR_UNREF(error);
+    return;
+  }
   call_data *calld = elem->call_data;
   grpc_op op;
   memset(&op, 0, sizeof(op));
diff --git a/src/core/lib/surface/server.h b/src/core/lib/surface/server.h
index 551a40a..a85d9f4 100644
--- a/src/core/lib/surface/server.h
+++ b/src/core/lib/surface/server.h
@@ -40,6 +40,9 @@
 
 extern const grpc_channel_filter grpc_server_top_filter;
 
+/** Lightweight tracing of server channel state */
+extern int grpc_server_channel_trace;
+
 /* Add a listener to the server: when the server starts, it will call start,
    and when it shuts down, it will call destroy */
 void grpc_server_add_listener(
diff --git a/src/core/lib/transport/connectivity_state.c b/src/core/lib/transport/connectivity_state.c
index 68d05e3..fdb5307 100644
--- a/src/core/lib/transport/connectivity_state.c
+++ b/src/core/lib/transport/connectivity_state.c
@@ -180,7 +180,8 @@
     *w->current = tracker->current_state;
     tracker->watchers = w->next;
     if (grpc_connectivity_state_trace) {
-      gpr_log(GPR_DEBUG, "NOTIFY: %p", w->notify);
+      gpr_log(GPR_DEBUG, "NOTIFY: %p %s: %p", tracker, tracker->name,
+              w->notify);
     }
     grpc_exec_ctx_sched(exec_ctx, w->notify,
                         GRPC_ERROR_REF(tracker->current_error), NULL);
diff --git a/src/core/lib/transport/static_metadata.c b/src/core/lib/transport/static_metadata.c
index 5e0352a..f019ef1 100644
--- a/src/core/lib/transport/static_metadata.c
+++ b/src/core/lib/transport/static_metadata.c
@@ -126,9 +126,9 @@
     "if-range",
     "if-unmodified-since",
     "last-modified",
+    "lb-cost",
+    "lb-token",
     "link",
-    "load-reporting-initial",
-    "load-reporting-trailing",
     "location",
     "max-forwards",
     ":method",
diff --git a/src/core/lib/transport/static_metadata.h b/src/core/lib/transport/static_metadata.h
index 5b9ee1a..e0a8196 100644
--- a/src/core/lib/transport/static_metadata.h
+++ b/src/core/lib/transport/static_metadata.h
@@ -175,12 +175,12 @@
 #define GRPC_MDSTR_IF_UNMODIFIED_SINCE (&grpc_static_mdstr_table[62])
 /* "last-modified" */
 #define GRPC_MDSTR_LAST_MODIFIED (&grpc_static_mdstr_table[63])
+/* "lb-cost" */
+#define GRPC_MDSTR_LB_COST (&grpc_static_mdstr_table[64])
+/* "lb-token" */
+#define GRPC_MDSTR_LB_TOKEN (&grpc_static_mdstr_table[65])
 /* "link" */
-#define GRPC_MDSTR_LINK (&grpc_static_mdstr_table[64])
-/* "load-reporting-initial" */
-#define GRPC_MDSTR_LOAD_REPORTING_INITIAL (&grpc_static_mdstr_table[65])
-/* "load-reporting-trailing" */
-#define GRPC_MDSTR_LOAD_REPORTING_TRAILING (&grpc_static_mdstr_table[66])
+#define GRPC_MDSTR_LINK (&grpc_static_mdstr_table[66])
 /* "location" */
 #define GRPC_MDSTR_LOCATION (&grpc_static_mdstr_table[67])
 /* "max-forwards" */
@@ -337,13 +337,12 @@
 #define GRPC_MDELEM_IF_UNMODIFIED_SINCE_EMPTY (&grpc_static_mdelem_table[44])
 /* "last-modified": "" */
 #define GRPC_MDELEM_LAST_MODIFIED_EMPTY (&grpc_static_mdelem_table[45])
+/* "lb-cost": "" */
+#define GRPC_MDELEM_LB_COST_EMPTY (&grpc_static_mdelem_table[46])
+/* "lb-token": "" */
+#define GRPC_MDELEM_LB_TOKEN_EMPTY (&grpc_static_mdelem_table[47])
 /* "link": "" */
-#define GRPC_MDELEM_LINK_EMPTY (&grpc_static_mdelem_table[46])
-/* "load-reporting-initial": "" */
-#define GRPC_MDELEM_LOAD_REPORTING_INITIAL_EMPTY (&grpc_static_mdelem_table[47])
-/* "load-reporting-trailing": "" */
-#define GRPC_MDELEM_LOAD_REPORTING_TRAILING_EMPTY \
-  (&grpc_static_mdelem_table[48])
+#define GRPC_MDELEM_LINK_EMPTY (&grpc_static_mdelem_table[48])
 /* "location": "" */
 #define GRPC_MDELEM_LOCATION_EMPTY (&grpc_static_mdelem_table[49])
 /* "max-forwards": "" */
diff --git a/src/core/lib/transport/transport.c b/src/core/lib/transport/transport.c
index 82fc605..75aec7a 100644
--- a/src/core/lib/transport/transport.c
+++ b/src/core/lib/transport/transport.c
@@ -46,8 +46,9 @@
 #ifdef GRPC_STREAM_REFCOUNT_DEBUG
 void grpc_stream_ref(grpc_stream_refcount *refcount, const char *reason) {
   gpr_atm val = gpr_atm_no_barrier_load(&refcount->refs.count);
-  gpr_log(GPR_DEBUG, "%s %p:%p   REF %d->%d %s", refcount->object_type,
-          refcount, refcount->destroy.cb_arg, (int)val, (int)val + 1, reason);
+  gpr_log(GPR_DEBUG, "%s %p:%p   REF %" PRIdPTR "->%" PRIdPTR " %s",
+          refcount->object_type, refcount, refcount->destroy.cb_arg, val,
+          val + 1, reason);
 #else
 void grpc_stream_ref(grpc_stream_refcount *refcount) {
 #endif
@@ -58,8 +59,9 @@
 void grpc_stream_unref(grpc_exec_ctx *exec_ctx, grpc_stream_refcount *refcount,
                        const char *reason) {
   gpr_atm val = gpr_atm_no_barrier_load(&refcount->refs.count);
-  gpr_log(GPR_DEBUG, "%s %p:%p UNREF %d->%d %s", refcount->object_type,
-          refcount, refcount->destroy.cb_arg, (int)val, (int)val - 1, reason);
+  gpr_log(GPR_DEBUG, "%s %p:%p UNREF %" PRIdPTR "->%" PRIdPTR " %s",
+          refcount->object_type, refcount, refcount->destroy.cb_arg, val,
+          val - 1, reason);
 #else
 void grpc_stream_unref(grpc_exec_ctx *exec_ctx,
                        grpc_stream_refcount *refcount) {
@@ -274,3 +276,28 @@
   op->op.on_consumed = &op->outer_on_complete;
   return &op->op;
 }
+
+typedef struct {
+  grpc_closure outer_on_complete;
+  grpc_closure *inner_on_complete;
+  grpc_transport_stream_op op;
+} made_transport_stream_op;
+
+static void destroy_made_transport_stream_op(grpc_exec_ctx *exec_ctx, void *arg,
+                                             grpc_error *error) {
+  made_transport_stream_op *op = arg;
+  grpc_exec_ctx_sched(exec_ctx, op->inner_on_complete, GRPC_ERROR_REF(error),
+                      NULL);
+  gpr_free(op);
+}
+
+grpc_transport_stream_op *grpc_make_transport_stream_op(
+    grpc_closure *on_complete) {
+  made_transport_stream_op *op = gpr_malloc(sizeof(*op));
+  grpc_closure_init(&op->outer_on_complete, destroy_made_transport_stream_op,
+                    op);
+  op->inner_on_complete = on_complete;
+  memset(&op->op, 0, sizeof(op->op));
+  op->op.on_complete = &op->outer_on_complete;
+  return &op->op;
+}
diff --git a/src/core/lib/transport/transport.h b/src/core/lib/transport/transport.h
index 8dc393f..50253eb 100644
--- a/src/core/lib/transport/transport.h
+++ b/src/core/lib/transport/transport.h
@@ -113,6 +113,10 @@
       have been completed. */
   grpc_closure *on_complete;
 
+  /** Is the completion of this op covered by a poller (if false: the op should
+      complete independently of some pollset being polled) */
+  bool covered_by_poller;
+
   /** Send initial metadata to the peer, from the provided metadata batch.
       idempotent_request MUST be set if this is non-null */
   grpc_metadata_batch *send_initial_metadata;
@@ -252,6 +256,7 @@
                                         gpr_slice *optional_message);
 
 char *grpc_transport_stream_op_string(grpc_transport_stream_op *op);
+char *grpc_transport_op_string(grpc_transport_op *op);
 
 /* Send a batch of operations on a transport
 
@@ -293,6 +298,10 @@
 /* Allocate a grpc_transport_op, and preconfigure the on_consumed closure to
    \a on_consumed and then delete the returned transport op */
 grpc_transport_op *grpc_make_transport_op(grpc_closure *on_consumed);
+/* Allocate a grpc_transport_stream_op, and preconfigure the on_consumed closure
+   to \a on_consumed and then delete the returned transport op */
+grpc_transport_stream_op *grpc_make_transport_stream_op(
+    grpc_closure *on_consumed);
 
 #ifdef __cplusplus
 }
diff --git a/src/core/lib/transport/transport_op_string.c b/src/core/lib/transport/transport_op_string.c
index 138591d..533ec52 100644
--- a/src/core/lib/transport/transport_op_string.c
+++ b/src/core/lib/transport/transport_op_string.c
@@ -41,6 +41,7 @@
 #include <grpc/support/string_util.h>
 #include <grpc/support/useful.h>
 #include "src/core/lib/support/string.h"
+#include "src/core/lib/transport/connectivity_state.h"
 
 /* These routines are here to facilitate debugging - they produce string
    representations of various transport data structures */
@@ -72,56 +73,51 @@
 char *grpc_transport_stream_op_string(grpc_transport_stream_op *op) {
   char *tmp;
   char *out;
-  int first = 1;
 
   gpr_strvec b;
   gpr_strvec_init(&b);
 
+  gpr_strvec_add(
+      &b, gpr_strdup(op->covered_by_poller ? "[COVERED]" : "[UNCOVERED]"));
+
   if (op->send_initial_metadata != NULL) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     gpr_strvec_add(&b, gpr_strdup("SEND_INITIAL_METADATA{"));
     put_metadata_list(&b, *op->send_initial_metadata);
     gpr_strvec_add(&b, gpr_strdup("}"));
   }
 
   if (op->send_message != NULL) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     gpr_asprintf(&tmp, "SEND_MESSAGE:flags=0x%08x:len=%d",
                  op->send_message->flags, op->send_message->length);
     gpr_strvec_add(&b, tmp);
   }
 
   if (op->send_trailing_metadata != NULL) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     gpr_strvec_add(&b, gpr_strdup("SEND_TRAILING_METADATA{"));
     put_metadata_list(&b, *op->send_trailing_metadata);
     gpr_strvec_add(&b, gpr_strdup("}"));
   }
 
   if (op->recv_initial_metadata != NULL) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     gpr_strvec_add(&b, gpr_strdup("RECV_INITIAL_METADATA"));
   }
 
   if (op->recv_message != NULL) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     gpr_strvec_add(&b, gpr_strdup("RECV_MESSAGE"));
   }
 
   if (op->recv_trailing_metadata != NULL) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     gpr_strvec_add(&b, gpr_strdup("RECV_TRAILING_METADATA"));
   }
 
   if (op->cancel_error != GRPC_ERROR_NONE) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     const char *msg = grpc_error_string(op->cancel_error);
     gpr_asprintf(&tmp, "CANCEL:%s", msg);
     grpc_error_free_string(msg);
@@ -129,8 +125,7 @@
   }
 
   if (op->close_error != GRPC_ERROR_NONE) {
-    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
-    first = 0;
+    gpr_strvec_add(&b, gpr_strdup(" "));
     const char *msg = grpc_error_string(op->close_error);
     gpr_asprintf(&tmp, "CLOSE:%s", msg);
     grpc_error_free_string(msg);
@@ -143,6 +138,82 @@
   return out;
 }
 
+char *grpc_transport_op_string(grpc_transport_op *op) {
+  char *tmp;
+  char *out;
+  bool first = true;
+
+  gpr_strvec b;
+  gpr_strvec_init(&b);
+
+  if (op->on_connectivity_state_change != NULL) {
+    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
+    first = false;
+    if (op->connectivity_state != NULL) {
+      gpr_asprintf(&tmp, "ON_CONNECTIVITY_STATE_CHANGE:p=%p:from=%s",
+                   op->on_connectivity_state_change,
+                   grpc_connectivity_state_name(*op->connectivity_state));
+      gpr_strvec_add(&b, tmp);
+    } else {
+      gpr_asprintf(&tmp, "ON_CONNECTIVITY_STATE_CHANGE:p=%p:unsubscribe",
+                   op->on_connectivity_state_change);
+      gpr_strvec_add(&b, tmp);
+    }
+  }
+
+  if (op->disconnect_with_error != GRPC_ERROR_NONE) {
+    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
+    first = false;
+    const char *err = grpc_error_string(op->disconnect_with_error);
+    gpr_asprintf(&tmp, "DISCONNECT:%s", err);
+    gpr_strvec_add(&b, tmp);
+    grpc_error_free_string(err);
+  }
+
+  if (op->send_goaway) {
+    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
+    first = false;
+    char *msg = op->goaway_message == NULL
+                    ? "null"
+                    : gpr_dump_slice(*op->goaway_message,
+                                     GPR_DUMP_ASCII | GPR_DUMP_HEX);
+    gpr_asprintf(&tmp, "SEND_GOAWAY:status=%d:msg=%s", op->goaway_status, msg);
+    if (op->goaway_message != NULL) gpr_free(msg);
+    gpr_strvec_add(&b, tmp);
+  }
+
+  if (op->set_accept_stream) {
+    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
+    first = false;
+    gpr_asprintf(&tmp, "SET_ACCEPT_STREAM:%p(%p,...)", op->set_accept_stream_fn,
+                 op->set_accept_stream_user_data);
+    gpr_strvec_add(&b, tmp);
+  }
+
+  if (op->bind_pollset != NULL) {
+    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
+    first = false;
+    gpr_strvec_add(&b, gpr_strdup("BIND_POLLSET"));
+  }
+
+  if (op->bind_pollset_set != NULL) {
+    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
+    first = false;
+    gpr_strvec_add(&b, gpr_strdup("BIND_POLLSET_SET"));
+  }
+
+  if (op->send_ping != NULL) {
+    if (!first) gpr_strvec_add(&b, gpr_strdup(" "));
+    first = false;
+    gpr_strvec_add(&b, gpr_strdup("SEND_PING"));
+  }
+
+  out = gpr_strvec_flatten(&b, NULL);
+  gpr_strvec_destroy(&b);
+
+  return out;
+}
+
 void grpc_call_log_op(char *file, int line, gpr_log_severity severity,
                       grpc_call_element *elem, grpc_transport_stream_op *op) {
   char *str = grpc_transport_stream_op_string(op);
diff --git a/src/core/lib/iomgr/workqueue_posix.h b/src/cpp/test/server_context_test_spouse.cc
similarity index 65%
rename from src/core/lib/iomgr/workqueue_posix.h
rename to src/cpp/test/server_context_test_spouse.cc
index 03ee21c..b93152e 100644
--- a/src/core/lib/iomgr/workqueue_posix.h
+++ b/src/cpp/test/server_context_test_spouse.cc
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,31 +31,22 @@
  *
  */
 
-#ifndef GRPC_CORE_LIB_IOMGR_WORKQUEUE_POSIX_H
-#define GRPC_CORE_LIB_IOMGR_WORKQUEUE_POSIX_H
+#include <grpc++/test/server_context_test_spouse.h>
 
-#include "src/core/lib/iomgr/wakeup_fd_posix.h"
-#include "src/core/lib/support/mpscq.h"
+namespace grpc {
+namespace testing {
 
-struct grpc_fd;
+void ServerContextTestSpouse::AddClientMetadata(const grpc::string& key,
+                                                const grpc::string& value) {
+  client_metadata_storage_.insert(
+      std::pair<grpc::string, grpc::string>(key, value));
+  ctx_->client_metadata_.clear();
+  for (auto iter = client_metadata_storage_.begin();
+       iter != client_metadata_storage_.end(); ++iter) {
+    ctx_->client_metadata_.insert(std::pair<grpc::string_ref, grpc::string_ref>(
+        iter->first.c_str(), iter->second.c_str()));
+  }
+}
 
-struct grpc_workqueue {
-  gpr_refcount refs;
-  gpr_mpscq queue;
-  // state is:
-  // lower bit - zero if orphaned
-  // other bits - number of items enqueued
-  gpr_atm state;
-
-  grpc_wakeup_fd wakeup_fd;
-  struct grpc_fd *wakeup_read_fd;
-
-  grpc_closure read_closure;
-};
-
-/** Create a work queue. Returns an error if creation fails. If creation
-    succeeds, sets *workqueue to point to it. */
-grpc_error *grpc_workqueue_create(grpc_exec_ctx *exec_ctx,
-                                  grpc_workqueue **workqueue);
-
-#endif /* GRPC_CORE_LIB_IOMGR_WORKQUEUE_POSIX_H */
+}  // namespace testing
+}  // namespace grpc
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 6a2f520..ebb7e00 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -277,20 +277,31 @@
         }
     }
 
-    internal class NoSuchMethodCallHandler : IServerCallHandler
+    internal class UnimplementedMethodCallHandler : IServerCallHandler
     {
-        public static readonly NoSuchMethodCallHandler Instance = new NoSuchMethodCallHandler();
+        public static readonly UnimplementedMethodCallHandler Instance = new UnimplementedMethodCallHandler();
 
-        public async Task HandleCall(ServerRpcNew newRpc, CompletionQueueSafeHandle cq)
+        DuplexStreamingServerCallHandler<byte[], byte[]> callHandlerImpl;
+
+        public UnimplementedMethodCallHandler()
         {
-            // We don't care about the payload type here.
-            var asyncCall = new AsyncCallServer<byte[], byte[]>(
-                (payload) => payload, (payload) => payload, newRpc.Server);
-            
-            asyncCall.Initialize(newRpc.Call, cq);
-            var finishedTask = asyncCall.ServerSideCallAsync();
-            await asyncCall.SendStatusFromServerAsync(new Status(StatusCode.Unimplemented, ""), Metadata.Empty, null).ConfigureAwait(false);
-            await finishedTask.ConfigureAwait(false);
+            var marshaller = new Marshaller<byte[]>((payload) => payload, (payload) => payload);
+            var method = new Method<byte[], byte[]>(MethodType.DuplexStreaming, "", "", marshaller, marshaller);
+            this.callHandlerImpl = new DuplexStreamingServerCallHandler<byte[], byte[]>(method, new DuplexStreamingServerMethod<byte[], byte[]>(UnimplementedMethod));
+        }
+
+        /// <summary>
+        /// Handler used for unimplemented method.
+        /// </summary>
+        private Task UnimplementedMethod(IAsyncStreamReader<byte[]> requestStream, IServerStreamWriter<byte[]> responseStream, ServerCallContext ctx)
+        {
+            ctx.Status = new Status(StatusCode.Unimplemented, "");
+            return Task.FromResult<object>(null);
+        }
+
+        public Task HandleCall(ServerRpcNew newRpc, CompletionQueueSafeHandle cq)
+        {
+            return callHandlerImpl.HandleCall(newRpc, cq);
         }
     }
 
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index 3b554e5..dd4a405 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -317,7 +317,7 @@
                 IServerCallHandler callHandler;
                 if (!callHandlers.TryGetValue(newRpc.Method, out callHandler))
                 {
-                    callHandler = NoSuchMethodCallHandler.Instance;
+                    callHandler = UnimplementedMethodCallHandler.Instance;
                 }
                 await callHandler.HandleCall(newRpc, cq).ConfigureAwait(false);
             }
diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php
index 124d324..72012a4 100755
--- a/src/php/tests/interop/interop_client.php
+++ b/src/php/tests/interop/interop_client.php
@@ -477,9 +477,11 @@
     list($result, $status) = $call->wait();
 
     hardAssert($status->code === 2,
-               'Received unexpected status code');
+               'Received unexpected UnaryCall status code: ' .
+               $status->code);
     hardAssert($status->details === 'test status message',
-               'Received unexpected status details');
+               'Received unexpected UnaryCall status details: ' .
+               $status->details);
 
     $streaming_call = $stub->FullDuplexCall();
 
@@ -487,12 +489,15 @@
     $streaming_request->setResponseStatus($echo_status);
     $streaming_call->write($streaming_request);
     $streaming_call->writesDone();
+    $result = $streaming_call->read();
 
     $status = $streaming_call->getStatus();
     hardAssert($status->code === 2,
-               'Received unexpected status code');
+               'Received unexpected FullDuplexCall status code: ' .
+               $status->code);
     hardAssert($status->details === 'test status message',
-               'Received unexpected status details');
+               'Received unexpected FullDuplexCall status details: ' .
+               $status->details);
 }
 
 function unimplementedMethod($stub)
diff --git a/src/proto/grpc/testing/test.proto b/src/proto/grpc/testing/test.proto
index b52c4cb..d6ef58a 100644
--- a/src/proto/grpc/testing/test.proto
+++ b/src/proto/grpc/testing/test.proto
@@ -74,6 +74,10 @@
   // first request.
   rpc HalfDuplexCall(stream StreamingOutputCallRequest)
       returns (stream StreamingOutputCallResponse);
+
+  // The test server will not implement this method. It will be used
+  // to test the behavior when clients call unimplemented methods.
+  rpc UnimplementedMethod(grpc.testing.Empty) returns (grpc.testing.Empty);
 }
 
 // A simple service NOT implemented at servers so clients can test for
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 2bebf48..406f88a 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -134,11 +134,11 @@
   '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_posix.c',
   'src/core/lib/iomgr/workqueue_windows.c',
   'src/core/lib/json/json.c',
   'src/core/lib/json/json_reader.c',
diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb
index 48bc61e..5ae4f25 100644
--- a/src/ruby/spec/generic/active_call_spec.rb
+++ b/src/ruby/spec/generic/active_call_spec.rb
@@ -137,6 +137,8 @@
         msg = 'message is a string'
         client_call.write_flag = f
         client_call.remote_send(msg)
+        # flush the message in case writes are set to buffered
+        call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil) if f == 1
 
         # confirm that the message was marshalled
         recvd_rpc =  @server.request_call
diff --git a/test/core/bad_client/tests/large_metadata.c b/test/core/bad_client/tests/large_metadata.c
index d106ce6..b9c8093 100644
--- a/test/core/bad_client/tests/large_metadata.c
+++ b/test/core/bad_client/tests/large_metadata.c
@@ -50,6 +50,7 @@
   "\x00\x00\x00\x04\x00\x00\x00\x00\x00" /* headers: generated from        \
                                             large_metadata.headers in this \
                                             directory */                   \
+  "\x00\x00\x00\x04\x01\x00\x00\x00\x00"                                   \
   "\x00"                                                                   \
   "5{\x01\x05\x00\x00\x00\x01"                                             \
   "\x10\x05:path\x08/foo/bar"                                              \
@@ -92,6 +93,7 @@
                                                                     in this                \
                                                                     directory              \
                                                                     */                     \
+  "\x00\x00\x00\x04\x01\x00\x00\x00\x00"                                                   \
   "\x00\x00\xc9\x01\x04\x00\x00\x00\x01"                                                   \
   "\x10\x05:path\x08/foo/bar"                                                              \
   "\x10\x07:scheme\x04http"                                                                \
diff --git a/test/core/end2end/cq_verifier.c b/test/core/end2end/cq_verifier.c
index 1f42d34..3e7e3f2 100644
--- a/test/core/end2end/cq_verifier.c
+++ b/test/core/end2end/cq_verifier.c
@@ -126,14 +126,14 @@
   return out;
 }
 
-static int byte_buffer_eq_slice(grpc_byte_buffer *bb, gpr_slice b) {
+int raw_byte_buffer_eq_slice(grpc_byte_buffer *rbb, gpr_slice b) {
   gpr_slice a;
   int ok;
 
-  if (!bb) return 0;
+  if (!rbb) return 0;
 
-  a = merge_slices(bb->data.raw.slice_buffer.slices,
-                   bb->data.raw.slice_buffer.count);
+  a = merge_slices(rbb->data.raw.slice_buffer.slices,
+                   rbb->data.raw.slice_buffer.count);
   ok = GPR_SLICE_LENGTH(a) == GPR_SLICE_LENGTH(b) &&
        0 == memcmp(GPR_SLICE_START_PTR(a), GPR_SLICE_START_PTR(b),
                    GPR_SLICE_LENGTH(a));
@@ -142,6 +142,21 @@
   return ok;
 }
 
+int byte_buffer_eq_slice(grpc_byte_buffer *bb, gpr_slice b) {
+  grpc_byte_buffer_reader reader;
+  grpc_byte_buffer *rbb;
+  int res;
+
+  GPR_ASSERT(grpc_byte_buffer_reader_init(&reader, bb) &&
+             "Couldn't init byte buffer reader");
+  rbb = grpc_raw_byte_buffer_from_reader(&reader);
+  res = raw_byte_buffer_eq_slice(rbb, b);
+  grpc_byte_buffer_reader_destroy(&reader);
+  grpc_byte_buffer_destroy(rbb);
+
+  return res;
+}
+
 int byte_buffer_eq_string(grpc_byte_buffer *bb, const char *str) {
   grpc_byte_buffer_reader reader;
   grpc_byte_buffer *rbb;
@@ -150,7 +165,7 @@
   GPR_ASSERT(grpc_byte_buffer_reader_init(&reader, bb) &&
              "Couldn't init byte buffer reader");
   rbb = grpc_raw_byte_buffer_from_reader(&reader);
-  res = byte_buffer_eq_slice(rbb, gpr_slice_from_copied_string(str));
+  res = raw_byte_buffer_eq_slice(rbb, gpr_slice_from_copied_string(str));
   grpc_byte_buffer_reader_destroy(&reader);
   grpc_byte_buffer_destroy(rbb);
 
diff --git a/test/core/end2end/cq_verifier.h b/test/core/end2end/cq_verifier.h
index 0a7c03c..47464fa 100644
--- a/test/core/end2end/cq_verifier.h
+++ b/test/core/end2end/cq_verifier.h
@@ -67,6 +67,7 @@
 #define CQ_EXPECT_COMPLETION(v, tag, success) \
   cq_expect_completion(v, __FILE__, __LINE__, tag, success)
 
+int byte_buffer_eq_slice(grpc_byte_buffer *bb, gpr_slice b);
 int byte_buffer_eq_string(grpc_byte_buffer *byte_buffer, const char *string);
 int contains_metadata(grpc_metadata_array *array, const char *key,
                       const char *value);
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/0083d5addbeca55271ed7ef93c8016bf7ca76903 b/test/core/end2end/fuzzers/client_fuzzer_corpus/0083d5addbeca55271ed7ef93c8016bf7ca76903
new file mode 100644
index 0000000..821d85d
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/0083d5addbeca55271ed7ef93c8016bf7ca76903
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/07b0bed3226eefac4a84000ec584e4ce06ebf1bf b/test/core/end2end/fuzzers/client_fuzzer_corpus/07b0bed3226eefac4a84000ec584e4ce06ebf1bf
new file mode 100644
index 0000000..8ba8dfa
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/07b0bed3226eefac4a84000ec584e4ce06ebf1bf
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/07cec5c8d9c856a910c6fb57da2ae954f44beed0 b/test/core/end2end/fuzzers/client_fuzzer_corpus/07cec5c8d9c856a910c6fb57da2ae954f44beed0
new file mode 100644
index 0000000..28c9554
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/07cec5c8d9c856a910c6fb57da2ae954f44beed0
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/0c27c9999302b39bf2256a90b0cdb767fb2b6fe3 b/test/core/end2end/fuzzers/client_fuzzer_corpus/0c27c9999302b39bf2256a90b0cdb767fb2b6fe3
new file mode 100644
index 0000000..a3ecb27
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/0c27c9999302b39bf2256a90b0cdb767fb2b6fe3
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/0d407f099f8418de3dd94bd2146c858a8c6575ad b/test/core/end2end/fuzzers/client_fuzzer_corpus/0d407f099f8418de3dd94bd2146c858a8c6575ad
new file mode 100644
index 0000000..ad20f50
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/0d407f099f8418de3dd94bd2146c858a8c6575ad
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/0d4d486aa9fd6e9c10cc9ca8967e922cadddb2fe b/test/core/end2end/fuzzers/client_fuzzer_corpus/0d4d486aa9fd6e9c10cc9ca8967e922cadddb2fe
new file mode 100644
index 0000000..fa6d96d
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/0d4d486aa9fd6e9c10cc9ca8967e922cadddb2fe
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/0d9ba07b57eb0e076b187c4455f662db085e730b b/test/core/end2end/fuzzers/client_fuzzer_corpus/0d9ba07b57eb0e076b187c4455f662db085e730b
new file mode 100644
index 0000000..bf6ec61
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/0d9ba07b57eb0e076b187c4455f662db085e730b
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/0f6b989cec08ef9da603dc83704d85900bd22f1f b/test/core/end2end/fuzzers/client_fuzzer_corpus/0f6b989cec08ef9da603dc83704d85900bd22f1f
new file mode 100644
index 0000000..6469f3c
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/0f6b989cec08ef9da603dc83704d85900bd22f1f
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/10b25b0726cb6d820165699e5a453691c7a9c343 b/test/core/end2end/fuzzers/client_fuzzer_corpus/10b25b0726cb6d820165699e5a453691c7a9c343
new file mode 100644
index 0000000..bda05de
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/10b25b0726cb6d820165699e5a453691c7a9c343
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/1231c6d007d9e43d169122348363e20d9f25ee93 b/test/core/end2end/fuzzers/client_fuzzer_corpus/1231c6d007d9e43d169122348363e20d9f25ee93
new file mode 100644
index 0000000..ed848cb
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/1231c6d007d9e43d169122348363e20d9f25ee93
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/13a9b61e431c20734c19bb36d85883b6a501284e b/test/core/end2end/fuzzers/client_fuzzer_corpus/13a9b61e431c20734c19bb36d85883b6a501284e
new file mode 100644
index 0000000..462273d
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/13a9b61e431c20734c19bb36d85883b6a501284e
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/1698ec182fad9d973b84615da3a683ecdf2d0b3b b/test/core/end2end/fuzzers/client_fuzzer_corpus/1698ec182fad9d973b84615da3a683ecdf2d0b3b
new file mode 100644
index 0000000..0c2710f
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/1698ec182fad9d973b84615da3a683ecdf2d0b3b
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/1859e2ee759e20fe195f67615a1576ce2b7d5bbd b/test/core/end2end/fuzzers/client_fuzzer_corpus/1859e2ee759e20fe195f67615a1576ce2b7d5bbd
new file mode 100644
index 0000000..ed91baf
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/1859e2ee759e20fe195f67615a1576ce2b7d5bbd
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/1a9017db5ad8a9dc6cfe72305da1683a87a73452 b/test/core/end2end/fuzzers/client_fuzzer_corpus/1a9017db5ad8a9dc6cfe72305da1683a87a73452
new file mode 100644
index 0000000..85d4be1
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/1a9017db5ad8a9dc6cfe72305da1683a87a73452
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/1bd90335afc9e0a1e6a9296e3cc27c03c1201886 b/test/core/end2end/fuzzers/client_fuzzer_corpus/1bd90335afc9e0a1e6a9296e3cc27c03c1201886
new file mode 100644
index 0000000..f020c9b
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/1bd90335afc9e0a1e6a9296e3cc27c03c1201886
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/1be157b0fc79f0e7e1e05dfa3cbbe1ad71528bc2 b/test/core/end2end/fuzzers/client_fuzzer_corpus/1be157b0fc79f0e7e1e05dfa3cbbe1ad71528bc2
new file mode 100644
index 0000000..ce32d23
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/1be157b0fc79f0e7e1e05dfa3cbbe1ad71528bc2
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/2185f411bdb1edc610f16ffc86836ae366193e03 b/test/core/end2end/fuzzers/client_fuzzer_corpus/2185f411bdb1edc610f16ffc86836ae366193e03
new file mode 100644
index 0000000..0516cfa
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/2185f411bdb1edc610f16ffc86836ae366193e03
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/22661803bd1c7198df4be6e08924ef6a48af9cd4 b/test/core/end2end/fuzzers/client_fuzzer_corpus/22661803bd1c7198df4be6e08924ef6a48af9cd4
new file mode 100644
index 0000000..2443c3b
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/22661803bd1c7198df4be6e08924ef6a48af9cd4
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/2717067bbc0e9bfc1d90d15cddf6154800a25ec6 b/test/core/end2end/fuzzers/client_fuzzer_corpus/2717067bbc0e9bfc1d90d15cddf6154800a25ec6
new file mode 100644
index 0000000..d0fe18d
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/2717067bbc0e9bfc1d90d15cddf6154800a25ec6
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/2825cfc19c9371f4fe70851283c68d49470d4d55 b/test/core/end2end/fuzzers/client_fuzzer_corpus/2825cfc19c9371f4fe70851283c68d49470d4d55
new file mode 100644
index 0000000..e1e52b9
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/2825cfc19c9371f4fe70851283c68d49470d4d55
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/29303c16f3afa18c2c0b84e77e587535a705a74c b/test/core/end2end/fuzzers/client_fuzzer_corpus/29303c16f3afa18c2c0b84e77e587535a705a74c
new file mode 100644
index 0000000..b684106
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/29303c16f3afa18c2c0b84e77e587535a705a74c
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/2b5eb5aac77af905877bd98ec2c4d746b247abb6 b/test/core/end2end/fuzzers/client_fuzzer_corpus/2b5eb5aac77af905877bd98ec2c4d746b247abb6
new file mode 100644
index 0000000..76655ea
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/2b5eb5aac77af905877bd98ec2c4d746b247abb6
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/2cc43573f271ecd332551c1fb34ebc8645eaefe8 b/test/core/end2end/fuzzers/client_fuzzer_corpus/2cc43573f271ecd332551c1fb34ebc8645eaefe8
new file mode 100644
index 0000000..25ead66
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/2cc43573f271ecd332551c1fb34ebc8645eaefe8
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/2feb41037f5dd34e9f3465a2fbf1a6d355c8ce9d b/test/core/end2end/fuzzers/client_fuzzer_corpus/2feb41037f5dd34e9f3465a2fbf1a6d355c8ce9d
new file mode 100644
index 0000000..9614683
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/2feb41037f5dd34e9f3465a2fbf1a6d355c8ce9d
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/300998021c7f743ff49d9cc192343ffd43eb47f2 b/test/core/end2end/fuzzers/client_fuzzer_corpus/300998021c7f743ff49d9cc192343ffd43eb47f2
new file mode 100644
index 0000000..a327645
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/300998021c7f743ff49d9cc192343ffd43eb47f2
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/310b2aff5e2ec78b6004630bed39d49f8d13bb21 b/test/core/end2end/fuzzers/client_fuzzer_corpus/310b2aff5e2ec78b6004630bed39d49f8d13bb21
new file mode 100644
index 0000000..e48ee01
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/310b2aff5e2ec78b6004630bed39d49f8d13bb21
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/3bb052abecc1b916cc869b9aad29c9dd55a95068 b/test/core/end2end/fuzzers/client_fuzzer_corpus/3bb052abecc1b916cc869b9aad29c9dd55a95068
new file mode 100644
index 0000000..e256230
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/3bb052abecc1b916cc869b9aad29c9dd55a95068
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/3c5fa483ebfabe6e684831ce7c413176bc998c33 b/test/core/end2end/fuzzers/client_fuzzer_corpus/3c5fa483ebfabe6e684831ce7c413176bc998c33
new file mode 100644
index 0000000..82c60ba
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/3c5fa483ebfabe6e684831ce7c413176bc998c33
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/3c6444b64ace5cd6c145614ad4412382271a6120 b/test/core/end2end/fuzzers/client_fuzzer_corpus/3c6444b64ace5cd6c145614ad4412382271a6120
new file mode 100644
index 0000000..a829aad
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/3c6444b64ace5cd6c145614ad4412382271a6120
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/4045d25f065bb1d70a8b9c3751f7453d4b0625b9 b/test/core/end2end/fuzzers/client_fuzzer_corpus/4045d25f065bb1d70a8b9c3751f7453d4b0625b9
new file mode 100644
index 0000000..d3d6384
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/4045d25f065bb1d70a8b9c3751f7453d4b0625b9
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/407cedf992b14edac6e19f7d440ab73c88e72465 b/test/core/end2end/fuzzers/client_fuzzer_corpus/407cedf992b14edac6e19f7d440ab73c88e72465
new file mode 100644
index 0000000..151a983
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/407cedf992b14edac6e19f7d440ab73c88e72465
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/411966ea7d9164fc432eeab55a55248ad808bb01 b/test/core/end2end/fuzzers/client_fuzzer_corpus/411966ea7d9164fc432eeab55a55248ad808bb01
new file mode 100644
index 0000000..b60637b
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/411966ea7d9164fc432eeab55a55248ad808bb01
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/415dde26637ed3c0e803111c532a1a9ea9c49092 b/test/core/end2end/fuzzers/client_fuzzer_corpus/415dde26637ed3c0e803111c532a1a9ea9c49092
new file mode 100644
index 0000000..2d8b9cf
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/415dde26637ed3c0e803111c532a1a9ea9c49092
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/4fc34239f220392581520aa8cebc659daa65a7a6 b/test/core/end2end/fuzzers/client_fuzzer_corpus/4fc34239f220392581520aa8cebc659daa65a7a6
new file mode 100644
index 0000000..1ca1c28
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/4fc34239f220392581520aa8cebc659daa65a7a6
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/52939682304314f04897deddfbc9c7afa8ee50a9 b/test/core/end2end/fuzzers/client_fuzzer_corpus/52939682304314f04897deddfbc9c7afa8ee50a9
new file mode 100644
index 0000000..27c5ca4
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/52939682304314f04897deddfbc9c7afa8ee50a9
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/5369926a559827d08bccf264876d592c7cae660d b/test/core/end2end/fuzzers/client_fuzzer_corpus/5369926a559827d08bccf264876d592c7cae660d
new file mode 100644
index 0000000..9a2c59f
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/5369926a559827d08bccf264876d592c7cae660d
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/53ef530f65b0cff2e338a51b469c224f53b628d7 b/test/core/end2end/fuzzers/client_fuzzer_corpus/53ef530f65b0cff2e338a51b469c224f53b628d7
new file mode 100644
index 0000000..cd12901
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/53ef530f65b0cff2e338a51b469c224f53b628d7
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/560c1057487e6b0d2d457748c3ad8434423eb263 b/test/core/end2end/fuzzers/client_fuzzer_corpus/560c1057487e6b0d2d457748c3ad8434423eb263
new file mode 100644
index 0000000..889056f
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/560c1057487e6b0d2d457748c3ad8434423eb263
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/564f203f678fb333c7b1f8f4df79237589ce346d b/test/core/end2end/fuzzers/client_fuzzer_corpus/564f203f678fb333c7b1f8f4df79237589ce346d
new file mode 100644
index 0000000..d64ca59
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/564f203f678fb333c7b1f8f4df79237589ce346d
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/56b0ac0636c57838f63415082b3ae2ec7a93f017 b/test/core/end2end/fuzzers/client_fuzzer_corpus/56b0ac0636c57838f63415082b3ae2ec7a93f017
new file mode 100644
index 0000000..f997ced
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/56b0ac0636c57838f63415082b3ae2ec7a93f017
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/58bcbd601894835bb3312d2a0bc56f2e0f65984c b/test/core/end2end/fuzzers/client_fuzzer_corpus/58bcbd601894835bb3312d2a0bc56f2e0f65984c
new file mode 100644
index 0000000..016a98e
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/58bcbd601894835bb3312d2a0bc56f2e0f65984c
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/598d346f284bcff26d1de997c4ba5c4794c90b68 b/test/core/end2end/fuzzers/client_fuzzer_corpus/598d346f284bcff26d1de997c4ba5c4794c90b68
new file mode 100644
index 0000000..1230998
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/598d346f284bcff26d1de997c4ba5c4794c90b68
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/5c14b48da74ab06b3cc20c4fe355e24f7dd7852a b/test/core/end2end/fuzzers/client_fuzzer_corpus/5c14b48da74ab06b3cc20c4fe355e24f7dd7852a
new file mode 100644
index 0000000..c0223af
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/5c14b48da74ab06b3cc20c4fe355e24f7dd7852a
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/5de72e607205dc17a45df703ec4e9b63c36821ec b/test/core/end2end/fuzzers/client_fuzzer_corpus/5de72e607205dc17a45df703ec4e9b63c36821ec
new file mode 100644
index 0000000..6f50853
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/5de72e607205dc17a45df703ec4e9b63c36821ec
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/5e25cf639ba8ea37543d944f5efa94824c6272ff b/test/core/end2end/fuzzers/client_fuzzer_corpus/5e25cf639ba8ea37543d944f5efa94824c6272ff
new file mode 100644
index 0000000..33b465f
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/5e25cf639ba8ea37543d944f5efa94824c6272ff
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/5f247d7b6753f7a8798cf952f49f303c532e017c b/test/core/end2end/fuzzers/client_fuzzer_corpus/5f247d7b6753f7a8798cf952f49f303c532e017c
new file mode 100644
index 0000000..922c3cc
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/5f247d7b6753f7a8798cf952f49f303c532e017c
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/63a1cb41d219394c9bab947202921506f3574ad0 b/test/core/end2end/fuzzers/client_fuzzer_corpus/63a1cb41d219394c9bab947202921506f3574ad0
new file mode 100644
index 0000000..417548a
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/63a1cb41d219394c9bab947202921506f3574ad0
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/650f74738d3961af2d1fe85ad8fc8212ea13cbbf b/test/core/end2end/fuzzers/client_fuzzer_corpus/650f74738d3961af2d1fe85ad8fc8212ea13cbbf
new file mode 100644
index 0000000..382f3de
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/650f74738d3961af2d1fe85ad8fc8212ea13cbbf
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/65dff388749da6a44926b491cdc555f61d708171 b/test/core/end2end/fuzzers/client_fuzzer_corpus/65dff388749da6a44926b491cdc555f61d708171
new file mode 100644
index 0000000..8e35246
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/65dff388749da6a44926b491cdc555f61d708171
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/676adbb1e5b3f4f9e3cba51d3d4ef963ba4ea7e3 b/test/core/end2end/fuzzers/client_fuzzer_corpus/676adbb1e5b3f4f9e3cba51d3d4ef963ba4ea7e3
new file mode 100644
index 0000000..720dcdb
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/676adbb1e5b3f4f9e3cba51d3d4ef963ba4ea7e3
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/67f160446ded73c408f4e5a0665731b642b6edd4 b/test/core/end2end/fuzzers/client_fuzzer_corpus/67f160446ded73c408f4e5a0665731b642b6edd4
new file mode 100644
index 0000000..cdbee59
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/67f160446ded73c408f4e5a0665731b642b6edd4
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/6856c7cb02d2ba74a60fd47140f042701dda63b3 b/test/core/end2end/fuzzers/client_fuzzer_corpus/6856c7cb02d2ba74a60fd47140f042701dda63b3
new file mode 100644
index 0000000..f2dc196
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/6856c7cb02d2ba74a60fd47140f042701dda63b3
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/69e14b73af03e8f2d998cfcf16215f65bf589efb b/test/core/end2end/fuzzers/client_fuzzer_corpus/69e14b73af03e8f2d998cfcf16215f65bf589efb
new file mode 100644
index 0000000..ae23d16
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/69e14b73af03e8f2d998cfcf16215f65bf589efb
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/77cff7548cafe87410e4a0dde3ba6892b25594d3 b/test/core/end2end/fuzzers/client_fuzzer_corpus/77cff7548cafe87410e4a0dde3ba6892b25594d3
new file mode 100644
index 0000000..95defaa
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/77cff7548cafe87410e4a0dde3ba6892b25594d3
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/7beeb19272131701f3a0d1dd633f1b1969899366 b/test/core/end2end/fuzzers/client_fuzzer_corpus/7beeb19272131701f3a0d1dd633f1b1969899366
new file mode 100644
index 0000000..4293d0c
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/7beeb19272131701f3a0d1dd633f1b1969899366
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/8b0cf53ac17015fe066002cb3814933df9ee96be b/test/core/end2end/fuzzers/client_fuzzer_corpus/8b0cf53ac17015fe066002cb3814933df9ee96be
new file mode 100644
index 0000000..ba952ec
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/8b0cf53ac17015fe066002cb3814933df9ee96be
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/8b5c4543923da5e468aca1de1ab880aed2ac4451 b/test/core/end2end/fuzzers/client_fuzzer_corpus/8b5c4543923da5e468aca1de1ab880aed2ac4451
new file mode 100644
index 0000000..7c5cd2b
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/8b5c4543923da5e468aca1de1ab880aed2ac4451
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/8d9784f85e9662734e180ca8bec2164425ae8a87 b/test/core/end2end/fuzzers/client_fuzzer_corpus/8d9784f85e9662734e180ca8bec2164425ae8a87
new file mode 100644
index 0000000..b2ad2b0
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/8d9784f85e9662734e180ca8bec2164425ae8a87
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/8e3f138d163022d6e105ab595788f4cfdd9b9db3 b/test/core/end2end/fuzzers/client_fuzzer_corpus/8e3f138d163022d6e105ab595788f4cfdd9b9db3
new file mode 100644
index 0000000..e10a35c
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/8e3f138d163022d6e105ab595788f4cfdd9b9db3
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/914464d372dcccf31ed5331293d84121e17616bb b/test/core/end2end/fuzzers/client_fuzzer_corpus/914464d372dcccf31ed5331293d84121e17616bb
new file mode 100644
index 0000000..c67d9de
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/914464d372dcccf31ed5331293d84121e17616bb
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/934a41b5027d1c5cca27ebda57560c38cb9e09ea b/test/core/end2end/fuzzers/client_fuzzer_corpus/934a41b5027d1c5cca27ebda57560c38cb9e09ea
new file mode 100644
index 0000000..8ca1a43
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/934a41b5027d1c5cca27ebda57560c38cb9e09ea
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/9354652806d96b09c8e7082b1b7d22e7c3fb9f0b b/test/core/end2end/fuzzers/client_fuzzer_corpus/9354652806d96b09c8e7082b1b7d22e7c3fb9f0b
new file mode 100644
index 0000000..41461a1
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/9354652806d96b09c8e7082b1b7d22e7c3fb9f0b
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/9398ac1c2b4015792661266a9c84b6d7a68c3155 b/test/core/end2end/fuzzers/client_fuzzer_corpus/9398ac1c2b4015792661266a9c84b6d7a68c3155
new file mode 100644
index 0000000..f9b2aaf
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/9398ac1c2b4015792661266a9c84b6d7a68c3155
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/99099024a3f3e389f57cb7b697eb34485846f316 b/test/core/end2end/fuzzers/client_fuzzer_corpus/99099024a3f3e389f57cb7b697eb34485846f316
new file mode 100644
index 0000000..1721f92
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/99099024a3f3e389f57cb7b697eb34485846f316
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/998a54dc94ab6e7d6a6066415fb0dd9b52356171 b/test/core/end2end/fuzzers/client_fuzzer_corpus/998a54dc94ab6e7d6a6066415fb0dd9b52356171
new file mode 100644
index 0000000..06ec60a
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/998a54dc94ab6e7d6a6066415fb0dd9b52356171
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/a25b31398669b585ccab97bceadc31994de7ead7 b/test/core/end2end/fuzzers/client_fuzzer_corpus/a25b31398669b585ccab97bceadc31994de7ead7
new file mode 100644
index 0000000..9d41026
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/a25b31398669b585ccab97bceadc31994de7ead7
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/a39ac9e92b41d1889096ed415b4c2eb1aba6ed50 b/test/core/end2end/fuzzers/client_fuzzer_corpus/a39ac9e92b41d1889096ed415b4c2eb1aba6ed50
new file mode 100644
index 0000000..be4a64b
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/a39ac9e92b41d1889096ed415b4c2eb1aba6ed50
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/a5c2fdae1a1c0487d00db0eec6e3429b12244b1f b/test/core/end2end/fuzzers/client_fuzzer_corpus/a5c2fdae1a1c0487d00db0eec6e3429b12244b1f
new file mode 100644
index 0000000..1c323b7
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/a5c2fdae1a1c0487d00db0eec6e3429b12244b1f
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/a649093880c2a2f143f861893eaff5d30be95eb7 b/test/core/end2end/fuzzers/client_fuzzer_corpus/a649093880c2a2f143f861893eaff5d30be95eb7
new file mode 100644
index 0000000..7b879ac
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/a649093880c2a2f143f861893eaff5d30be95eb7
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/a8249ebfe91327806446f14a6b2e7d9c8440257f b/test/core/end2end/fuzzers/client_fuzzer_corpus/a8249ebfe91327806446f14a6b2e7d9c8440257f
new file mode 100644
index 0000000..2bdbe51
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/a8249ebfe91327806446f14a6b2e7d9c8440257f
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/a8e306820fb76566b522c23ec68bdce0ad0536f2 b/test/core/end2end/fuzzers/client_fuzzer_corpus/a8e306820fb76566b522c23ec68bdce0ad0536f2
new file mode 100644
index 0000000..3c62087
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/a8e306820fb76566b522c23ec68bdce0ad0536f2
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/a97dbb159ef9bc6e39c9c25e04315752e871e739 b/test/core/end2end/fuzzers/client_fuzzer_corpus/a97dbb159ef9bc6e39c9c25e04315752e871e739
new file mode 100644
index 0000000..b43e55b
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/a97dbb159ef9bc6e39c9c25e04315752e871e739
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/ae448bfe17f9a3a6eff074d4caa9f7261c94d2d5 b/test/core/end2end/fuzzers/client_fuzzer_corpus/ae448bfe17f9a3a6eff074d4caa9f7261c94d2d5
new file mode 100644
index 0000000..5d16743
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/ae448bfe17f9a3a6eff074d4caa9f7261c94d2d5
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/ae8cdc02275a1436bc131bee52a17ee797e2e6c9 b/test/core/end2end/fuzzers/client_fuzzer_corpus/ae8cdc02275a1436bc131bee52a17ee797e2e6c9
new file mode 100644
index 0000000..12c2446
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/ae8cdc02275a1436bc131bee52a17ee797e2e6c9
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/b10353c265bef989d8909055fd6cd52e49eef3e6 b/test/core/end2end/fuzzers/client_fuzzer_corpus/b10353c265bef989d8909055fd6cd52e49eef3e6
new file mode 100644
index 0000000..4f6e5cc
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/b10353c265bef989d8909055fd6cd52e49eef3e6
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/b387e46c23912785e6c353ab49b8ea4a92c2c2e5 b/test/core/end2end/fuzzers/client_fuzzer_corpus/b387e46c23912785e6c353ab49b8ea4a92c2c2e5
new file mode 100644
index 0000000..32397f0
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/b387e46c23912785e6c353ab49b8ea4a92c2c2e5
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/b3cfcd55b0331ab0c931b8c61d4df41464587f10 b/test/core/end2end/fuzzers/client_fuzzer_corpus/b3cfcd55b0331ab0c931b8c61d4df41464587f10
new file mode 100644
index 0000000..fd118bc
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/b3cfcd55b0331ab0c931b8c61d4df41464587f10
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/b758f5c019696f33c50895168219c0e6cb04e11d b/test/core/end2end/fuzzers/client_fuzzer_corpus/b758f5c019696f33c50895168219c0e6cb04e11d
new file mode 100644
index 0000000..6b74b8a
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/b758f5c019696f33c50895168219c0e6cb04e11d
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/b93fd0a15287dd035eac86e547e3ce42183bdb28 b/test/core/end2end/fuzzers/client_fuzzer_corpus/b93fd0a15287dd035eac86e547e3ce42183bdb28
new file mode 100644
index 0000000..0bb5774
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/b93fd0a15287dd035eac86e547e3ce42183bdb28
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/ba3566735888b53712c6b2e6d52ff5f2197afd6a b/test/core/end2end/fuzzers/client_fuzzer_corpus/ba3566735888b53712c6b2e6d52ff5f2197afd6a
new file mode 100644
index 0000000..3d91afe
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/ba3566735888b53712c6b2e6d52ff5f2197afd6a
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/bd275178fd473028a5cedf7d5780b27e809882ee b/test/core/end2end/fuzzers/client_fuzzer_corpus/bd275178fd473028a5cedf7d5780b27e809882ee
new file mode 100644
index 0000000..eb3276b
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/bd275178fd473028a5cedf7d5780b27e809882ee
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/c4c53b4727e9e1f040c5d7870639dd3daa184ddb b/test/core/end2end/fuzzers/client_fuzzer_corpus/c4c53b4727e9e1f040c5d7870639dd3daa184ddb
new file mode 100644
index 0000000..80e75c5
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/c4c53b4727e9e1f040c5d7870639dd3daa184ddb
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/c9e2cf8be8a4dc2294020026c62840ef1fb4853b b/test/core/end2end/fuzzers/client_fuzzer_corpus/c9e2cf8be8a4dc2294020026c62840ef1fb4853b
new file mode 100644
index 0000000..e97dce0
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/c9e2cf8be8a4dc2294020026c62840ef1fb4853b
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/caaf9a7751c0eccc34f0fc00a048012ab5ed2f37 b/test/core/end2end/fuzzers/client_fuzzer_corpus/caaf9a7751c0eccc34f0fc00a048012ab5ed2f37
new file mode 100644
index 0000000..f2374b7
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/caaf9a7751c0eccc34f0fc00a048012ab5ed2f37
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/cb49955601d171fd14c9ac21137b221392c7dab1 b/test/core/end2end/fuzzers/client_fuzzer_corpus/cb49955601d171fd14c9ac21137b221392c7dab1
new file mode 100644
index 0000000..adfce80
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/cb49955601d171fd14c9ac21137b221392c7dab1
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/cbaabef34763f2fd922e67ff5f2ea283347e9823 b/test/core/end2end/fuzzers/client_fuzzer_corpus/cbaabef34763f2fd922e67ff5f2ea283347e9823
new file mode 100644
index 0000000..3a937ae
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/cbaabef34763f2fd922e67ff5f2ea283347e9823
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/cdd1a4e358ee2396ece54b32c1f0a8d0a2e3f3dc b/test/core/end2end/fuzzers/client_fuzzer_corpus/cdd1a4e358ee2396ece54b32c1f0a8d0a2e3f3dc
new file mode 100644
index 0000000..82466bf
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/cdd1a4e358ee2396ece54b32c1f0a8d0a2e3f3dc
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/cf922d44bf08d223d3ebcd37a7e77d3e43555d08 b/test/core/end2end/fuzzers/client_fuzzer_corpus/cf922d44bf08d223d3ebcd37a7e77d3e43555d08
new file mode 100644
index 0000000..b9399a2
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/cf922d44bf08d223d3ebcd37a7e77d3e43555d08
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-17d5b79ce495f7d3f2e33d95588457281a5e8965 b/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-17d5b79ce495f7d3f2e33d95588457281a5e8965
new file mode 100644
index 0000000..7b5b32c
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-17d5b79ce495f7d3f2e33d95588457281a5e8965
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-3e52af52deb0ed5d6ef06487d7e475e8fb616972 b/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-3e52af52deb0ed5d6ef06487d7e475e8fb616972
new file mode 100644
index 0000000..30614ec
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-3e52af52deb0ed5d6ef06487d7e475e8fb616972
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-ff53a3d713e83ae945b8dd1782e21f5b51aa649a b/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-ff53a3d713e83ae945b8dd1782e21f5b51aa649a
new file mode 100644
index 0000000..28ad75e
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/crash-ff53a3d713e83ae945b8dd1782e21f5b51aa649a
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/d17e9507af1855fcf9eca78e2d25c8fb2c40a34c b/test/core/end2end/fuzzers/client_fuzzer_corpus/d17e9507af1855fcf9eca78e2d25c8fb2c40a34c
new file mode 100644
index 0000000..b262a23
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/d17e9507af1855fcf9eca78e2d25c8fb2c40a34c
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/d46c3dcede830286dd9f4a1ba02a20a0b1430664 b/test/core/end2end/fuzzers/client_fuzzer_corpus/d46c3dcede830286dd9f4a1ba02a20a0b1430664
new file mode 100644
index 0000000..a11ecbe
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/d46c3dcede830286dd9f4a1ba02a20a0b1430664
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/d4a744ef6dcef5cf08d5289e167b26270d39e9f2 b/test/core/end2end/fuzzers/client_fuzzer_corpus/d4a744ef6dcef5cf08d5289e167b26270d39e9f2
new file mode 100644
index 0000000..462391f
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/d4a744ef6dcef5cf08d5289e167b26270d39e9f2
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/d5a85ad91cfde27a96960b2e783d2ee43c50dcb9 b/test/core/end2end/fuzzers/client_fuzzer_corpus/d5a85ad91cfde27a96960b2e783d2ee43c50dcb9
new file mode 100644
index 0000000..c187ee3
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/d5a85ad91cfde27a96960b2e783d2ee43c50dcb9
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/d88bb0b7ff687af84f33e6af22d3516fcdac5534 b/test/core/end2end/fuzzers/client_fuzzer_corpus/d88bb0b7ff687af84f33e6af22d3516fcdac5534
new file mode 100644
index 0000000..a64fe17
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/d88bb0b7ff687af84f33e6af22d3516fcdac5534
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/d895ece988ad4712b87de8aa9bc273eee315e8b8 b/test/core/end2end/fuzzers/client_fuzzer_corpus/d895ece988ad4712b87de8aa9bc273eee315e8b8
new file mode 100644
index 0000000..615ac35
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/d895ece988ad4712b87de8aa9bc273eee315e8b8
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/da424090e1b94c5d0e91e26f3f3dd6c4af18fcd5 b/test/core/end2end/fuzzers/client_fuzzer_corpus/da424090e1b94c5d0e91e26f3f3dd6c4af18fcd5
new file mode 100644
index 0000000..99ae121
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/da424090e1b94c5d0e91e26f3f3dd6c4af18fcd5
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/db3a30a6d8e605dd587e51b214c42f68bc43cf19 b/test/core/end2end/fuzzers/client_fuzzer_corpus/db3a30a6d8e605dd587e51b214c42f68bc43cf19
new file mode 100644
index 0000000..cc05683
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/db3a30a6d8e605dd587e51b214c42f68bc43cf19
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/e3d12a2385b75443fe38d989e77c252e1f3cdb6d b/test/core/end2end/fuzzers/client_fuzzer_corpus/e3d12a2385b75443fe38d989e77c252e1f3cdb6d
new file mode 100644
index 0000000..1878ff2
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/e3d12a2385b75443fe38d989e77c252e1f3cdb6d
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/e4f55281c481484bd9edc28fd10df0c2e0f7d546 b/test/core/end2end/fuzzers/client_fuzzer_corpus/e4f55281c481484bd9edc28fd10df0c2e0f7d546
new file mode 100644
index 0000000..042b169
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/e4f55281c481484bd9edc28fd10df0c2e0f7d546
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/e6c52f2f31db7595d1ecde2939a7390777f15182 b/test/core/end2end/fuzzers/client_fuzzer_corpus/e6c52f2f31db7595d1ecde2939a7390777f15182
new file mode 100644
index 0000000..17fbabe
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/e6c52f2f31db7595d1ecde2939a7390777f15182
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f09cd3e3a16658174717668e51e7382e491df1da b/test/core/end2end/fuzzers/client_fuzzer_corpus/f09cd3e3a16658174717668e51e7382e491df1da
new file mode 100644
index 0000000..82c3e4f
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f09cd3e3a16658174717668e51e7382e491df1da
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f11abb090bae8cdac1f7d9a2e344f2def0e50066 b/test/core/end2end/fuzzers/client_fuzzer_corpus/f11abb090bae8cdac1f7d9a2e344f2def0e50066
new file mode 100644
index 0000000..2a12fb9
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f11abb090bae8cdac1f7d9a2e344f2def0e50066
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f4ae2a2b692bfa83cdde75d007813426e14daef7 b/test/core/end2end/fuzzers/client_fuzzer_corpus/f4ae2a2b692bfa83cdde75d007813426e14daef7
new file mode 100644
index 0000000..ea1c77e
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f4ae2a2b692bfa83cdde75d007813426e14daef7
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f5a629c8fd5720236b66a875e96ea22e29c45965 b/test/core/end2end/fuzzers/client_fuzzer_corpus/f5a629c8fd5720236b66a875e96ea22e29c45965
new file mode 100644
index 0000000..8b2a877
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f5a629c8fd5720236b66a875e96ea22e29c45965
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f6627c55881fe4f0c8e6999980fb226836e6f5ce b/test/core/end2end/fuzzers/client_fuzzer_corpus/f6627c55881fe4f0c8e6999980fb226836e6f5ce
new file mode 100644
index 0000000..60c8c75
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f6627c55881fe4f0c8e6999980fb226836e6f5ce
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f7aeceaf0b6d971038a677994b5d080fa0e18011 b/test/core/end2end/fuzzers/client_fuzzer_corpus/f7aeceaf0b6d971038a677994b5d080fa0e18011
new file mode 100644
index 0000000..a80c582
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f7aeceaf0b6d971038a677994b5d080fa0e18011
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f803c87a92662898e2c8c847787b56d2c31f63b3 b/test/core/end2end/fuzzers/client_fuzzer_corpus/f803c87a92662898e2c8c847787b56d2c31f63b3
new file mode 100644
index 0000000..146a748
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f803c87a92662898e2c8c847787b56d2c31f63b3
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f89ad475ff51a5a9fe18603df833453bed320f36 b/test/core/end2end/fuzzers/client_fuzzer_corpus/f89ad475ff51a5a9fe18603df833453bed320f36
new file mode 100644
index 0000000..ae4812d
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f89ad475ff51a5a9fe18603df833453bed320f36
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/f9583b3a39c1aecbba6e81d71e7fe9b9519c8b08 b/test/core/end2end/fuzzers/client_fuzzer_corpus/f9583b3a39c1aecbba6e81d71e7fe9b9519c8b08
new file mode 100644
index 0000000..9018d4d
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/f9583b3a39c1aecbba6e81d71e7fe9b9519c8b08
Binary files differ
diff --git a/test/core/end2end/fuzzers/client_fuzzer_corpus/ffd52d31f9c59a346aa195a683f077dda5ecef6b b/test/core/end2end/fuzzers/client_fuzzer_corpus/ffd52d31f9c59a346aa195a683f077dda5ecef6b
new file mode 100644
index 0000000..d7e73ad
--- /dev/null
+++ b/test/core/end2end/fuzzers/client_fuzzer_corpus/ffd52d31f9c59a346aa195a683f077dda5ecef6b
Binary files differ
diff --git a/test/core/end2end/fuzzers/hpack.dictionary b/test/core/end2end/fuzzers/hpack.dictionary
index a93bccf..181bbe8 100644
--- a/test/core/end2end/fuzzers/hpack.dictionary
+++ b/test/core/end2end/fuzzers/hpack.dictionary
@@ -63,9 +63,9 @@
 "\x08if-range"
 "\x13if-unmodified-since"
 "\x0Dlast-modified"
+"\x07lb-cost"
+"\x08lb-token"
 "\x04link"
-"\x16load-reporting-initial"
-"\x17load-reporting-trailing"
 "\x08location"
 "\x0Cmax-forwards"
 "\x07:method"
@@ -138,9 +138,9 @@
 "\x00\x08if-range\x00"
 "\x00\x13if-unmodified-since\x00"
 "\x00\x0Dlast-modified\x00"
+"\x00\x07lb-cost\x00"
+"\x00\x08lb-token\x00"
 "\x00\x04link\x00"
-"\x00\x16load-reporting-initial\x00"
-"\x00\x17load-reporting-trailing\x00"
 "\x00\x08location\x00"
 "\x00\x0Cmax-forwards\x00"
 "\x00\x07:method\x03GET"
diff --git a/test/core/end2end/tests/filter_causes_close.c b/test/core/end2end/tests/filter_causes_close.c
index cdf8683..f14bb32 100644
--- a/test/core/end2end/tests/filter_causes_close.c
+++ b/test/core/end2end/tests/filter_causes_close.c
@@ -209,11 +209,10 @@
     // close the stream with an error.
     gpr_slice message =
         gpr_slice_from_copied_string("Failure that's not preventable.");
-    grpc_transport_stream_op op;
-    memset(&op, 0, sizeof(op));
-    grpc_transport_stream_op_add_close(&op, GRPC_STATUS_PERMISSION_DENIED,
+    grpc_transport_stream_op *op = grpc_make_transport_stream_op(NULL);
+    grpc_transport_stream_op_add_close(op, GRPC_STATUS_PERMISSION_DENIED,
                                        &message);
-    grpc_call_next_op(exec_ctx, elem, &op);
+    grpc_call_next_op(exec_ctx, elem, op);
   }
   grpc_exec_ctx_sched(
       exec_ctx, calld->recv_im_ready,
diff --git a/test/core/end2end/tests/load_reporting_hook.c b/test/core/end2end/tests/load_reporting_hook.c
index 59d054c..7f95dfd 100644
--- a/test/core/end2end/tests/load_reporting_hook.c
+++ b/test/core/end2end/tests/load_reporting_hook.c
@@ -295,13 +295,13 @@
   grpc_metadata initial_lr_metadata;
   grpc_metadata trailing_lr_metadata;
 
-  initial_lr_metadata.key = GRPC_LOAD_REPORTING_INITIAL_MD_KEY;
+  initial_lr_metadata.key = GRPC_LB_TOKEN_MD_KEY;
   initial_lr_metadata.value = "client-token";
   initial_lr_metadata.value_length = strlen(initial_lr_metadata.value);
   memset(&initial_lr_metadata.internal_data, 0,
          sizeof(initial_lr_metadata.internal_data));
 
-  trailing_lr_metadata.key = GRPC_LOAD_REPORTING_TRAILING_MD_KEY;
+  trailing_lr_metadata.key = GRPC_LB_COST_MD_KEY;
   trailing_lr_metadata.value = "server-token";
   trailing_lr_metadata.value_length = strlen(trailing_lr_metadata.value);
   memset(&trailing_lr_metadata.internal_data, 0,
diff --git a/test/core/end2end/tests/no_logging.c b/test/core/end2end/tests/no_logging.c
index 430bfdc..1d3cfda 100644
--- a/test/core/end2end/tests/no_logging.c
+++ b/test/core/end2end/tests/no_logging.c
@@ -293,6 +293,8 @@
 }
 
 void no_logging(grpc_end2end_test_config config) {
+  gpr_set_log_verbosity(GPR_LOG_SEVERITY_DEBUG);
+  grpc_tracer_set_enabled("all", 0);
   gpr_set_log_function(log_dispatcher_func);
   test_no_logging_in_one_request(config);
   test_no_error_logging_in_entire_process(config);
diff --git a/test/core/end2end/tests/payload.c b/test/core/end2end/tests/payload.c
index c71704f..40696d0 100644
--- a/test/core/end2end/tests/payload.c
+++ b/test/core/end2end/tests/payload.c
@@ -95,9 +95,29 @@
   grpc_completion_queue_destroy(f->cq);
 }
 
+/* Creates and returns a gpr_slice containing random alphanumeric characters. */
+static gpr_slice generate_random_slice() {
+  size_t i;
+  static const char chars[] = "abcdefghijklmnopqrstuvwxyz1234567890";
+  char *output;
+  const size_t output_size = 1024 * 1024;
+  output = gpr_malloc(output_size);
+  for (i = 0; i < output_size - 1; ++i) {
+    output[i] = chars[rand() % (int)(sizeof(chars) - 1)];
+  }
+  output[output_size - 1] = '\0';
+  gpr_slice out = gpr_slice_from_copied_string(output);
+  gpr_free(output);
+  return out;
+}
+
 static void request_response_with_payload(grpc_end2end_test_fixture f) {
-  gpr_slice request_payload_slice = gpr_slice_from_copied_string("hello world");
-  gpr_slice response_payload_slice = gpr_slice_from_copied_string("hello you");
+  /* Create large request and response bodies. These are big enough to require
+   * multiple round trips to deliver to the peer, and their exact contents of
+   * will be verified on completion. */
+  gpr_slice request_payload_slice = generate_random_slice();
+  gpr_slice response_payload_slice = generate_random_slice();
+
   grpc_call *c;
   grpc_call *s;
   grpc_byte_buffer *request_payload =
@@ -222,8 +242,9 @@
   GPR_ASSERT(0 == strcmp(call_details.method, "/foo"));
   GPR_ASSERT(0 == strcmp(call_details.host, "foo.test.google.fr"));
   GPR_ASSERT(was_cancelled == 0);
-  GPR_ASSERT(byte_buffer_eq_string(request_payload_recv, "hello world"));
-  GPR_ASSERT(byte_buffer_eq_string(response_payload_recv, "hello you"));
+  GPR_ASSERT(byte_buffer_eq_slice(request_payload_recv, request_payload_slice));
+  GPR_ASSERT(
+      byte_buffer_eq_slice(response_payload_recv, response_payload_slice));
 
   gpr_free(details);
   grpc_metadata_array_destroy(&initial_metadata_recv);
diff --git a/test/core/iomgr/combiner_test.c b/test/core/iomgr/combiner_test.c
index 197998c..f7d5809 100644
--- a/test/core/iomgr/combiner_test.c
+++ b/test/core/iomgr/combiner_test.c
@@ -61,7 +61,7 @@
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
   grpc_combiner_execute(&exec_ctx, lock,
                         grpc_closure_create(set_bool_to_true, &done),
-                        GRPC_ERROR_NONE);
+                        GRPC_ERROR_NONE, false);
   grpc_exec_ctx_flush(&exec_ctx);
   GPR_ASSERT(done);
   grpc_combiner_destroy(&exec_ctx, lock);
@@ -95,7 +95,8 @@
       c->ctr = &args->ctr;
       c->value = n++;
       grpc_combiner_execute(&exec_ctx, args->lock,
-                            grpc_closure_create(check_one, c), GRPC_ERROR_NONE);
+                            grpc_closure_create(check_one, c), GRPC_ERROR_NONE,
+                            false);
       grpc_exec_ctx_flush(&exec_ctx);
     }
     // sleep for a little bit, to test a combiner draining and another thread
@@ -144,7 +145,7 @@
   grpc_combiner *lock = grpc_combiner_create(NULL);
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
   grpc_combiner_execute(&exec_ctx, lock, grpc_closure_create(add_finally, lock),
-                        GRPC_ERROR_NONE);
+                        GRPC_ERROR_NONE, false);
   grpc_exec_ctx_flush(&exec_ctx);
   GPR_ASSERT(got_in_finally);
   grpc_combiner_destroy(&exec_ctx, lock);
diff --git a/test/core/iomgr/wakeup_fd_cv_test.c b/test/core/iomgr/wakeup_fd_cv_test.c
new file mode 100644
index 0000000..952985e
--- /dev/null
+++ b/test/core/iomgr/wakeup_fd_cv_test.c
@@ -0,0 +1,240 @@
+/*
+ *
+ * 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 <pthread.h>
+
+#include <grpc/support/log.h>
+#include <grpc/support/thd.h>
+#include <grpc/support/time.h>
+#include <grpc/support/useful.h>
+
+#include "src/core/lib/iomgr/ev_posix.h"
+#include "src/core/lib/iomgr/iomgr_posix.h"
+#include "src/core/lib/support/env.h"
+
+typedef struct poll_args {
+  struct pollfd *fds;
+  nfds_t nfds;
+  int timeout;
+  int result;
+} poll_args;
+
+gpr_cv poll_cv;
+gpr_mu poll_mu;
+static int socket_event = 0;
+
+// Trigger a "socket" POLLIN in mock_poll()
+void trigger_socket_event() {
+  gpr_mu_lock(&poll_mu);
+  socket_event = 1;
+  gpr_cv_broadcast(&poll_cv);
+  gpr_mu_unlock(&poll_mu);
+}
+
+void reset_socket_event() {
+  gpr_mu_lock(&poll_mu);
+  socket_event = 0;
+  gpr_mu_unlock(&poll_mu);
+}
+
+// Mocks posix poll() function
+int mock_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
+  int res = 0;
+  gpr_timespec poll_time;
+  gpr_mu_lock(&poll_mu);
+  GPR_ASSERT(nfds == 3);
+  GPR_ASSERT(fds[0].fd == 20);
+  GPR_ASSERT(fds[1].fd == 30);
+  GPR_ASSERT(fds[2].fd == 50);
+  GPR_ASSERT(fds[0].events == (POLLIN | POLLHUP));
+  GPR_ASSERT(fds[1].events == (POLLIN | POLLHUP));
+  GPR_ASSERT(fds[2].events == POLLIN);
+
+  if (timeout < 0) {
+    poll_time = gpr_inf_future(GPR_CLOCK_REALTIME);
+  } else {
+    poll_time = gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
+                             gpr_time_from_millis(timeout, GPR_TIMESPAN));
+  }
+
+  if (socket_event || !gpr_cv_wait(&poll_cv, &poll_mu, poll_time)) {
+    fds[0].revents = POLLIN;
+    res = 1;
+  }
+  gpr_mu_unlock(&poll_mu);
+  return res;
+}
+
+void background_poll(void *args) {
+  poll_args *pargs = (poll_args *)args;
+  pargs->result = grpc_poll_function(pargs->fds, pargs->nfds, pargs->timeout);
+}
+
+void test_many_fds(void) {
+  int i;
+  grpc_wakeup_fd fd[1000];
+  for (i = 0; i < 1000; i++) {
+    GPR_ASSERT(grpc_wakeup_fd_init(&fd[i]) == GRPC_ERROR_NONE);
+  }
+  for (i = 0; i < 1000; i++) {
+    grpc_wakeup_fd_destroy(&fd[i]);
+  }
+}
+
+void test_poll_cv_trigger(void) {
+  grpc_wakeup_fd cvfd1, cvfd2, cvfd3;
+  struct pollfd pfds[6];
+  poll_args pargs;
+  gpr_thd_id t_id;
+  gpr_thd_options opt;
+
+  GPR_ASSERT(grpc_wakeup_fd_init(&cvfd1) == GRPC_ERROR_NONE);
+  GPR_ASSERT(grpc_wakeup_fd_init(&cvfd2) == GRPC_ERROR_NONE);
+  GPR_ASSERT(grpc_wakeup_fd_init(&cvfd3) == GRPC_ERROR_NONE);
+  GPR_ASSERT(cvfd1.read_fd < 0);
+  GPR_ASSERT(cvfd2.read_fd < 0);
+  GPR_ASSERT(cvfd3.read_fd < 0);
+  GPR_ASSERT(cvfd1.read_fd != cvfd2.read_fd);
+  GPR_ASSERT(cvfd2.read_fd != cvfd3.read_fd);
+  GPR_ASSERT(cvfd1.read_fd != cvfd3.read_fd);
+
+  pfds[0].fd = cvfd1.read_fd;
+  pfds[1].fd = cvfd2.read_fd;
+  pfds[2].fd = 20;
+  pfds[3].fd = 30;
+  pfds[4].fd = cvfd3.read_fd;
+  pfds[5].fd = 50;
+
+  pfds[0].events = 0;
+  pfds[1].events = POLLIN;
+  pfds[2].events = POLLIN | POLLHUP;
+  pfds[3].events = POLLIN | POLLHUP;
+  pfds[4].events = POLLIN;
+  pfds[5].events = POLLIN;
+
+  pargs.fds = pfds;
+  pargs.nfds = 6;
+  pargs.timeout = 1000;
+  pargs.result = -2;
+
+  opt = gpr_thd_options_default();
+  gpr_thd_options_set_joinable(&opt);
+  gpr_thd_new(&t_id, &background_poll, &pargs, &opt);
+
+  // Wakeup wakeup_fd not listening for events
+  GPR_ASSERT(grpc_wakeup_fd_wakeup(&cvfd1) == GRPC_ERROR_NONE);
+  gpr_thd_join(t_id);
+  GPR_ASSERT(pargs.result == 0);
+  GPR_ASSERT(pfds[0].revents == 0);
+  GPR_ASSERT(pfds[1].revents == 0);
+  GPR_ASSERT(pfds[2].revents == 0);
+  GPR_ASSERT(pfds[3].revents == 0);
+  GPR_ASSERT(pfds[4].revents == 0);
+  GPR_ASSERT(pfds[5].revents == 0);
+
+  // Pollin on socket fd
+  pargs.timeout = -1;
+  pargs.result = -2;
+  gpr_thd_new(&t_id, &background_poll, &pargs, &opt);
+  trigger_socket_event();
+  gpr_thd_join(t_id);
+  GPR_ASSERT(pargs.result == 1);
+  GPR_ASSERT(pfds[0].revents == 0);
+  GPR_ASSERT(pfds[1].revents == 0);
+  GPR_ASSERT(pfds[2].revents == POLLIN);
+  GPR_ASSERT(pfds[3].revents == 0);
+  GPR_ASSERT(pfds[4].revents == 0);
+  GPR_ASSERT(pfds[5].revents == 0);
+
+  // Pollin on wakeup fd
+  reset_socket_event();
+  pargs.result = -2;
+  gpr_thd_new(&t_id, &background_poll, &pargs, &opt);
+  GPR_ASSERT(grpc_wakeup_fd_wakeup(&cvfd2) == GRPC_ERROR_NONE);
+  gpr_thd_join(t_id);
+
+  GPR_ASSERT(pargs.result == 1);
+  GPR_ASSERT(pfds[0].revents == 0);
+  GPR_ASSERT(pfds[1].revents == POLLIN);
+  GPR_ASSERT(pfds[2].revents == 0);
+  GPR_ASSERT(pfds[3].revents == 0);
+  GPR_ASSERT(pfds[4].revents == 0);
+  GPR_ASSERT(pfds[5].revents == 0);
+
+  // Pollin on wakeup fd + socket fd
+  trigger_socket_event();
+  pargs.result = -2;
+  gpr_thd_new(&t_id, &background_poll, &pargs, &opt);
+  gpr_thd_join(t_id);
+
+  GPR_ASSERT(pargs.result == 2);
+  GPR_ASSERT(pfds[0].revents == 0);
+  GPR_ASSERT(pfds[1].revents == POLLIN);
+  GPR_ASSERT(pfds[2].revents == POLLIN);
+  GPR_ASSERT(pfds[3].revents == 0);
+  GPR_ASSERT(pfds[4].revents == 0);
+  GPR_ASSERT(pfds[5].revents == 0);
+
+  // No Events
+  pargs.result = -2;
+  pargs.timeout = 1000;
+  reset_socket_event();
+  GPR_ASSERT(grpc_wakeup_fd_consume_wakeup(&cvfd1) == GRPC_ERROR_NONE);
+  GPR_ASSERT(grpc_wakeup_fd_consume_wakeup(&cvfd2) == GRPC_ERROR_NONE);
+  gpr_thd_new(&t_id, &background_poll, &pargs, &opt);
+  gpr_thd_join(t_id);
+
+  GPR_ASSERT(pargs.result == 0);
+  GPR_ASSERT(pfds[0].revents == 0);
+  GPR_ASSERT(pfds[1].revents == 0);
+  GPR_ASSERT(pfds[2].revents == 0);
+  GPR_ASSERT(pfds[3].revents == 0);
+  GPR_ASSERT(pfds[4].revents == 0);
+  GPR_ASSERT(pfds[5].revents == 0);
+}
+
+int main(int argc, char **argv) {
+  gpr_setenv("GRPC_POLL_STRATEGY", "poll-cv");
+  grpc_poll_function = &mock_poll;
+  gpr_mu_init(&poll_mu);
+  gpr_cv_init(&poll_cv);
+
+  grpc_iomgr_platform_init();
+  test_many_fds();
+  grpc_iomgr_platform_shutdown();
+
+  grpc_iomgr_platform_init();
+  test_poll_cv_trigger();
+  grpc_iomgr_platform_shutdown();
+  return 0;
+}
diff --git a/test/core/support/string_test.c b/test/core/support/string_test.c
index 553a824..378e45a 100644
--- a/test/core/support/string_test.c
+++ b/test/core/support/string_test.c
@@ -366,6 +366,13 @@
   gpr_free(padded);
 }
 
+static void test_stricmp(void) {
+  GPR_ASSERT(0 == gpr_stricmp("hello", "hello"));
+  GPR_ASSERT(0 == gpr_stricmp("HELLO", "hello"));
+  GPR_ASSERT(gpr_stricmp("a", "b") < 0);
+  GPR_ASSERT(gpr_stricmp("b", "a") > 0);
+}
+
 int main(int argc, char **argv) {
   grpc_test_init(argc, argv);
   test_strdup();
@@ -379,5 +386,6 @@
   test_ltoa();
   test_int64toa();
   test_leftpad();
+  test_stricmp();
   return 0;
 }
diff --git a/test/core/transport/chttp2/hpack_parser_fuzzer_test.c b/test/core/transport/chttp2/hpack_parser_fuzzer_test.c
index b7f68e0..95acbf1 100644
--- a/test/core/transport/chttp2/hpack_parser_fuzzer_test.c
+++ b/test/core/transport/chttp2/hpack_parser_fuzzer_test.c
@@ -43,7 +43,9 @@
 bool squelch = true;
 bool leak_check = true;
 
-static void onhdr(void *ud, grpc_mdelem *md) { GRPC_MDELEM_UNREF(md); }
+static void onhdr(grpc_exec_ctx *exec_ctx, void *ud, grpc_mdelem *md) {
+  GRPC_MDELEM_UNREF(md);
+}
 static void dont_log(gpr_log_func_args *args) {}
 
 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
@@ -53,7 +55,10 @@
   grpc_chttp2_hpack_parser parser;
   grpc_chttp2_hpack_parser_init(&parser);
   parser.on_header = onhdr;
-  GRPC_ERROR_UNREF(grpc_chttp2_hpack_parser_parse(&parser, data, data + size));
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  GRPC_ERROR_UNREF(
+      grpc_chttp2_hpack_parser_parse(&exec_ctx, &parser, data, data + size));
+  grpc_exec_ctx_finish(&exec_ctx);
   grpc_chttp2_hpack_parser_destroy(&parser);
   grpc_shutdown();
   return 0;
diff --git a/test/core/transport/chttp2/hpack_parser_test.c b/test/core/transport/chttp2/hpack_parser_test.c
index 9ddceb8..55b64f5 100644
--- a/test/core/transport/chttp2/hpack_parser_test.c
+++ b/test/core/transport/chttp2/hpack_parser_test.c
@@ -45,7 +45,7 @@
 
 typedef struct { va_list args; } test_checker;
 
-static void onhdr(void *ud, grpc_mdelem *md) {
+static void onhdr(grpc_exec_ctx *exec_ctx, void *ud, grpc_mdelem *md) {
   const char *ekey, *evalue;
   test_checker *chk = ud;
   ekey = va_arg(chk->args, char *);
@@ -75,9 +75,11 @@
   gpr_slice_unref(input);
 
   for (i = 0; i < nslices; i++) {
+    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
     GPR_ASSERT(grpc_chttp2_hpack_parser_parse(
-                   parser, GPR_SLICE_START_PTR(slices[i]),
+                   &exec_ctx, parser, GPR_SLICE_START_PTR(slices[i]),
                    GPR_SLICE_END_PTR(slices[i])) == GRPC_ERROR_NONE);
+    grpc_exec_ctx_finish(&exec_ctx);
   }
 
   for (i = 0; i < nslices; i++) {
diff --git a/test/cpp/end2end/thread_stress_test.cc b/test/cpp/end2end/thread_stress_test.cc
index b021b34..0b9d4cd 100644
--- a/test/cpp/end2end/thread_stress_test.cc
+++ b/test/cpp/end2end/thread_stress_test.cc
@@ -339,7 +339,11 @@
     ClientContext context;
     Status s = stub->Echo(&context, request, &response);
     EXPECT_EQ(response.message(), request.message());
-    EXPECT_TRUE(s.ok());
+    if (!s.ok()) {
+      gpr_log(GPR_ERROR, "RPC error: %d: %s", s.error_code(),
+              s.error_message().c_str());
+    }
+    ASSERT_TRUE(s.ok());
   }
 }
 
diff --git a/test/cpp/grpclb/grpclb_test.cc b/test/cpp/grpclb/grpclb_test.cc
index 7666c4e..80f2fa4 100644
--- a/test/cpp/grpclb/grpclb_test.cc
+++ b/test/cpp/grpclb/grpclb_test.cc
@@ -334,8 +334,7 @@
     // have a version for int but does have one for long long int.
     string expected_token = "token" + std::to_string((long long int)sf->port);
     expected_token.resize(64, '-');
-    GPR_ASSERT(contains_metadata(&request_metadata_recv,
-                                 "load-reporting-initial",
+    GPR_ASSERT(contains_metadata(&request_metadata_recv, "lb-token",
                                  expected_token.c_str()));
 
     gpr_log(GPR_INFO, "Server[%s] after tag 100", sf->servers_hostport);
diff --git a/test/cpp/interop/client.cc b/test/cpp/interop/client.cc
index 999be9d..245e27b 100644
--- a/test/cpp/interop/client.cc
+++ b/test/cpp/interop/client.cc
@@ -79,7 +79,8 @@
               "slow_consumer : single request with response streaming with "
               "slow client consumer;\n"
               "status_code_and_message: verify status code & message;\n"
-              "timeout_on_sleeping_server: deadline exceeds on stream;\n");
+              "timeout_on_sleeping_server: deadline exceeds on stream;\n"
+              "unimplemented_method: client calls an unimplemented_method;\n");
 DEFINE_string(default_service_account, "",
               "Email of GCE default service account");
 DEFINE_string(service_account_key_file, "",
@@ -149,6 +150,8 @@
     client.DoStatusWithMessage();
   } else if (FLAGS_test_case == "custom_metadata") {
     client.DoCustomMetadata();
+  } else if (FLAGS_test_case == "unimplemented_method") {
+    client.DoUnimplementedMethod();
   } else if (FLAGS_test_case == "cacheable_unary") {
     client.DoCacheableUnary();
   } else if (FLAGS_test_case == "all") {
@@ -168,6 +171,7 @@
     client.DoEmptyStream();
     client.DoStatusWithMessage();
     client.DoCustomMetadata();
+    client.DoUnimplementedMethod();
     client.DoCacheableUnary();
     // service_account_creds and jwt_token_creds can only run with ssl.
     if (FLAGS_use_tls) {
@@ -202,7 +206,8 @@
                                "server_compressed_unary",
                                "server_streaming",
                                "status_code_and_message",
-                               "timeout_on_sleeping_server"};
+                               "timeout_on_sleeping_server",
+                               "unimplemented_method"};
     char* joined_testcases =
         gpr_strjoin_sep(testcases, GPR_ARRAY_SIZE(testcases), "\n", NULL);
 
diff --git a/test/cpp/interop/interop_client.cc b/test/cpp/interop/interop_client.cc
index e9a804c..f95d8c6 100644
--- a/test/cpp/interop/interop_client.cc
+++ b/test/cpp/interop/interop_client.cc
@@ -827,21 +827,42 @@
   gpr_log(GPR_DEBUG,
           "Sending RPC with a request for status code 2 and message");
 
+  const grpc::StatusCode test_code = grpc::StatusCode::UNKNOWN;
+  const grpc::string test_msg = "This is a test message";
+
+  // Test UnaryCall.
   ClientContext context;
   SimpleRequest request;
   SimpleResponse response;
   EchoStatus* requested_status = request.mutable_response_status();
-  requested_status->set_code(grpc::StatusCode::UNKNOWN);
-  grpc::string test_msg = "This is a test message";
+  requested_status->set_code(test_code);
   requested_status->set_message(test_msg);
-
   Status s = serviceStub_.Get()->UnaryCall(&context, request, &response);
-
   if (!AssertStatusCode(s, grpc::StatusCode::UNKNOWN)) {
     return false;
   }
-
   GPR_ASSERT(s.error_message() == test_msg);
+
+  // Test FullDuplexCall.
+  ClientContext stream_context;
+  std::shared_ptr<ClientReaderWriter<StreamingOutputCallRequest,
+                                     StreamingOutputCallResponse>>
+      stream(serviceStub_.Get()->FullDuplexCall(&stream_context));
+  StreamingOutputCallRequest streaming_request;
+  requested_status = streaming_request.mutable_response_status();
+  requested_status->set_code(test_code);
+  requested_status->set_message(test_msg);
+  stream->Write(streaming_request);
+  stream->WritesDone();
+  StreamingOutputCallResponse streaming_response;
+  while (stream->Read(&streaming_response))
+    ;
+  s = stream->Finish();
+  if (!AssertStatusCode(s, grpc::StatusCode::UNKNOWN)) {
+    return false;
+  }
+  GPR_ASSERT(s.error_message() == test_msg);
+
   gpr_log(GPR_DEBUG, "Done testing Status and Message");
   return true;
 }
@@ -981,5 +1002,23 @@
   return true;
 }
 
+bool InteropClient::DoUnimplementedMethod() {
+  gpr_log(GPR_DEBUG, "Sending a request for an unimplemented rpc...");
+
+  Empty request = Empty::default_instance();
+  Empty response = Empty::default_instance();
+  ClientContext context;
+
+  Status s =
+      serviceStub_.Get()->UnimplementedMethod(&context, request, &response);
+
+  if (!AssertStatusCode(s, StatusCode::UNIMPLEMENTED)) {
+    return false;
+  }
+
+  gpr_log(GPR_DEBUG, "unimplemented rpc done.");
+  return true;
+}
+
 }  // namespace testing
 }  // namespace grpc
diff --git a/test/cpp/interop/interop_client.h b/test/cpp/interop/interop_client.h
index 1e89f09..0a96e77 100644
--- a/test/cpp/interop/interop_client.h
+++ b/test/cpp/interop/interop_client.h
@@ -79,6 +79,7 @@
   bool DoEmptyStream();
   bool DoStatusWithMessage();
   bool DoCustomMetadata();
+  bool DoUnimplementedMethod();
   bool DoCacheableUnary();
   // Auth tests.
   // username is a string containing the user email
diff --git a/test/cpp/interop/interop_server.cc b/test/cpp/interop/interop_server.cc
index 58f20aa..8b50ae8 100644
--- a/test/cpp/interop/interop_server.cc
+++ b/test/cpp/interop/interop_server.cc
@@ -269,6 +269,11 @@
     StreamingOutputCallResponse response;
     bool write_success = true;
     while (write_success && stream->Read(&request)) {
+      if (request.has_response_status()) {
+        return Status(
+            static_cast<grpc::StatusCode>(request.response_status().code()),
+            request.response_status().message());
+      }
       if (request.response_parameters_size() != 0) {
         response.mutable_payload()->set_type(request.payload().type());
         response.mutable_payload()->set_body(
diff --git a/test/cpp/qps/client_sync.cc b/test/cpp/qps/client_sync.cc
index 8062424..0ccf4e2 100644
--- a/test/cpp/qps/client_sync.cc
+++ b/test/cpp/qps/client_sync.cc
@@ -130,6 +130,10 @@
     grpc::Status s =
         stub->UnaryCall(&context, request_, &responses_[thread_idx]);
     entry->set_value((UsageTimer::Now() - start) * 1e9);
+    if (!s.ok()) {
+      gpr_log(GPR_ERROR, "RPC error: %d: %s", s.error_code(),
+              s.error_message().c_str());
+    }
     return s.ok();
   }
 };
diff --git a/test/cpp/qps/driver.cc b/test/cpp/qps/driver.cc
index 7460bb5..6c675e3 100644
--- a/test/cpp/qps/driver.cc
+++ b/test/cpp/qps/driver.cc
@@ -45,6 +45,7 @@
 #include <grpc/support/host_port.h>
 #include <grpc/support/log.h>
 
+#include "src/core/lib/profiling/timers.h"
 #include "src/core/lib/support/env.h"
 #include "src/proto/grpc/testing/services.grpc.pb.h"
 #include "test/core/util/port.h"
@@ -438,6 +439,8 @@
       start,
       gpr_time_from_seconds(warmup_seconds + benchmark_seconds, GPR_TIMESPAN)));
 
+  gpr_timer_set_enabled(0);
+
   // Finish a run
   std::unique_ptr<ScenarioResult> result(new ScenarioResult);
   Histogram merged_latencies;
diff --git a/test/cpp/qps/server_async.cc b/test/cpp/qps/server_async.cc
index 082b4bc..73a96a8 100644
--- a/test/cpp/qps/server_async.cc
+++ b/test/cpp/qps/server_async.cc
@@ -131,9 +131,7 @@
       std::lock_guard<std::mutex> lock((*ss)->mutex);
       (*ss)->shutdown = true;
     }
-    // TODO (vpai): Remove this deadline and allow Shutdown to finish properly
-    auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(3);
-    server_->Shutdown(deadline);
+    std::thread shutdown_thread(&AsyncQpsServerTest::ShutdownThreadFunc, this);
     for (auto cq = srv_cqs_.begin(); cq != srv_cqs_.end(); ++cq) {
       (*cq)->Shutdown();
     }
@@ -146,9 +144,16 @@
       while ((*cq)->Next(&got_tag, &ok))
         ;
     }
+    shutdown_thread.join();
   }
 
  private:
+  void ShutdownThreadFunc() {
+    // TODO (vpai): Remove this deadline and allow Shutdown to finish properly
+    auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(3);
+    server_->Shutdown(deadline);
+  }
+
   void ThreadFunc(int thread_idx) {
     // Wait until work is available or we are shutting down
     bool ok;
diff --git a/test/cpp/test/server_context_test_spouse_test.cc b/test/cpp/test/server_context_test_spouse_test.cc
new file mode 100644
index 0000000..e0d6a2f
--- /dev/null
+++ b/test/cpp/test/server_context_test_spouse_test.cc
@@ -0,0 +1,108 @@
+/*
+ *
+ * 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++/test/server_context_test_spouse.h>
+
+#include <cstring>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace grpc {
+namespace testing {
+
+const char key1[] = "metadata-key1";
+const char key2[] = "metadata-key2";
+const char val1[] = "metadata-val1";
+const char val2[] = "metadata-val2";
+
+bool ClientMetadataContains(const ServerContext& context,
+                            const grpc::string_ref& key,
+                            const grpc::string_ref& value) {
+  const auto& client_metadata = context.client_metadata();
+  for (auto iter = client_metadata.begin(); iter != client_metadata.end();
+       ++iter) {
+    if (iter->first == key && iter->second == value) {
+      return true;
+    }
+  }
+  return false;
+}
+
+TEST(ServerContextTestSpouseTest, ClientMetadata) {
+  ServerContext context;
+  ServerContextTestSpouse spouse(&context);
+
+  spouse.AddClientMetadata(key1, val1);
+  ASSERT_TRUE(ClientMetadataContains(context, key1, val1));
+
+  spouse.AddClientMetadata(key2, val2);
+  ASSERT_TRUE(ClientMetadataContains(context, key1, val1));
+  ASSERT_TRUE(ClientMetadataContains(context, key2, val2));
+}
+
+TEST(ServerContextTestSpouseTest, InitialMetadata) {
+  ServerContext context;
+  ServerContextTestSpouse spouse(&context);
+  std::multimap<grpc::string, grpc::string> metadata;
+
+  context.AddInitialMetadata(key1, val1);
+  metadata.insert(std::pair<grpc::string, grpc::string>(key1, val1));
+  ASSERT_EQ(metadata, spouse.GetInitialMetadata());
+
+  context.AddInitialMetadata(key2, val2);
+  metadata.insert(std::pair<grpc::string, grpc::string>(key2, val2));
+  ASSERT_EQ(metadata, spouse.GetInitialMetadata());
+}
+
+TEST(ServerContextTestSpouseTest, TrailingMetadata) {
+  ServerContext context;
+  ServerContextTestSpouse spouse(&context);
+  std::multimap<grpc::string, grpc::string> metadata;
+
+  context.AddTrailingMetadata(key1, val1);
+  metadata.insert(std::pair<grpc::string, grpc::string>(key1, val1));
+  ASSERT_EQ(metadata, spouse.GetTrailingMetadata());
+
+  context.AddTrailingMetadata(key2, val2);
+  metadata.insert(std::pair<grpc::string, grpc::string>(key2, val2));
+  ASSERT_EQ(metadata, spouse.GetTrailingMetadata());
+}
+
+}  // namespace
+}  // namespace grpc
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/tools/codegen/core/gen_static_metadata.py b/tools/codegen/core/gen_static_metadata.py
index 6f40010..6f8cad2 100755
--- a/tools/codegen/core/gen_static_metadata.py
+++ b/tools/codegen/core/gen_static_metadata.py
@@ -109,8 +109,8 @@
     ('if-range', ''),
     ('if-unmodified-since', ''),
     ('last-modified', ''),
-    ('load-reporting-initial', ''),
-    ('load-reporting-trailing', ''),
+    ('lb-token', ''),
+    ('lb-cost', ''),
     ('link', ''),
     ('location', ''),
     ('max-forwards', ''),
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 44f147a..3848675 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -841,10 +841,10 @@
 src/core/lib/iomgr/timer_heap.h \
 src/core/lib/iomgr/udp_server.h \
 src/core/lib/iomgr/unix_sockets_posix.h \
+src/core/lib/iomgr/wakeup_fd_cv.h \
 src/core/lib/iomgr/wakeup_fd_pipe.h \
 src/core/lib/iomgr/wakeup_fd_posix.h \
 src/core/lib/iomgr/workqueue.h \
-src/core/lib/iomgr/workqueue_posix.h \
 src/core/lib/iomgr/workqueue_windows.h \
 src/core/lib/json/json.h \
 src/core/lib/json/json_common.h \
@@ -1007,11 +1007,11 @@
 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_posix.c \
 src/core/lib/iomgr/workqueue_windows.c \
 src/core/lib/json/json.c \
 src/core/lib/json/json_reader.c \
diff --git a/tools/gce/create_linux_performance_worker.sh b/tools/gce/create_linux_performance_worker.sh
index c9a0ffa..2c8cf0b 100755
--- a/tools/gce/create_linux_performance_worker.sh
+++ b/tools/gce/create_linux_performance_worker.sh
@@ -48,7 +48,8 @@
     --project="$CLOUD_PROJECT" \
     --zone "$ZONE" \
     --machine-type $MACHINE_TYPE \
-    --image ubuntu-15-10 \
+    --image-project ubuntu-os-cloud \
+    --image-family ubuntu-1604-lts \
     --boot-disk-size 300 \
     --scopes https://www.googleapis.com/auth/bigquery
 
diff --git a/tools/gce/linux_performance_worker_init.sh b/tools/gce/linux_performance_worker_init.sh
index 4876725..523749e 100755
--- a/tools/gce/linux_performance_worker_init.sh
+++ b/tools/gce/linux_performance_worker_init.sh
@@ -90,6 +90,16 @@
 # Python dependencies
 sudo pip install tabulate
 sudo pip install google-api-python-client
+sudo pip install virtualenv
+
+# TODO(jtattermusch): For some reason, building gRPC Python depends on python3.4
+# being installed, but python3.4 is not available on Ubuntu 16.04.
+# Temporarily fixing this by adding a PPA with python3.4, but we should
+# really remove this hack once possible.
+sudo add-apt-repository -y ppa:fkrull/deadsnakes
+sudo apt-get update
+sudo apt-get install -y python3.4 python3.4-dev
+python3.4 -m pip install virtualenv
 
 curl -O https://bootstrap.pypa.io/get-pip.py
 sudo pypy get-pip.py
@@ -105,17 +115,17 @@
 nvm install 5 && npm config set cache /tmp/npm-cache
 nvm alias default 4
 
-# C# dependencies (http://www.mono-project.com/docs/getting-started/install/linux/#debian-ubuntu-and-derivatives)
-
+# C# mono dependencies (http://www.mono-project.com/docs/getting-started/install/linux/#debian-ubuntu-and-derivatives)
 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
 echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list
 sudo apt-get update
 sudo apt-get install -y mono-devel nuget
 
-# The version of nuget that is installed using apt-get is too old to download
-# the System.Interactive.Async.3.0.0 C# dependency. Update to the latest version
-# in order to be able download it.
-sudo nuget update -self
+# C# .NET Core dependencies (https://www.microsoft.com/net/core#ubuntu)
+sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
+sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
+sudo apt-get update
+sudo apt-get install -y dotnet-dev-1.0.0-preview2-003131
 
 # Ruby dependencies
 gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
diff --git a/tools/jenkins/run_sweep_performance.sh b/tools/jenkins/run_sweep_performance.sh
index 6cd7a91..3b22e25 100755
--- a/tools/jenkins/run_sweep_performance.sh
+++ b/tools/jenkins/run_sweep_performance.sh
@@ -31,6 +31,9 @@
 # This script is invoked by Jenkins and runs full performance test suite.
 set -ex
 
+SERVER_HOST=${1:-grpc-performance-server-32core}
+CLIENT_HOST1=${2:-grpc-performance-client-32core}
+CLIENT_HOST2=${3:-grpc-performance-client2-32core}
 # Enter the gRPC repo root
 cd $(dirname $0)/../..
 
@@ -39,7 +42,7 @@
     -l c++ \
     --category sweep \
     --bq_result_table performance_test.performance_experiment_32core \
-    --remote_worker_host grpc-performance-server-32core grpc-performance-client-32core grpc-performance-client2-32core \
+    --remote_worker_host ${SERVER_HOST} ${CLIENT_HOST1} ${CLIENT_HOST2} \
     || EXIT_CODE=1
 
 exit $EXIT_CODE
diff --git a/tools/profiling/perf/run_perf_unconstrained.sh b/tools/profiling/perf/run_perf_unconstrained.sh
new file mode 100755
index 0000000..dbfd282
--- /dev/null
+++ b/tools/profiling/perf/run_perf_unconstrained.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# 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.
+
+# format argument via
+# $ echo '{...}' | python -mjson.tool
+read -r -d '' SCENARIOS_JSON_ARG <<'EOF'
+{
+    "scenarios": [
+        {
+            "benchmark_seconds": 60,
+            "warmup_seconds": 5,
+            "client_config": {
+                "client_channels": 100,
+                "client_type": "ASYNC_CLIENT",
+                "histogram_params": {
+                    "max_possible": 60000000000.0,
+                    "resolution": 0.01
+                },
+                "load_params": {
+                    "closed_loop": {}
+                },
+                "outstanding_rpcs_per_channel": 100,
+                "payload_config": {
+                    "simple_params": {
+                        "req_size": 0,
+                        "resp_size": 0
+                    }
+                },
+                "rpc_type": "UNARY",
+                "security_params": null
+            },
+            "name": "name_goes_here",
+            "num_clients": 1,
+            "num_servers": 1,
+            "server_config": {
+                "security_params": null,
+                "server_type": "ASYNC_SERVER"
+            },
+            "spawn_local_worker_count": -2
+        }
+    ]
+}
+
+EOF
+
+set -ex
+
+cd $(dirname $0)/../../..
+
+CPUS=`python -c 'import multiprocessing; print multiprocessing.cpu_count()'`
+
+# try to use pypy for generating reports
+# each trace dumps 7-8gig of text to disk, and processing this into a report is
+# heavyweight - so any speed boost is worthwhile
+# TODO(ctiller): consider rewriting report generation in C++ for performance
+if which pypy >/dev/null; then
+  PYTHON=pypy
+else
+  PYTHON=python2.7
+fi
+
+export config=mutrace
+
+make CONFIG=$config -j$CPUS qps_json_driver
+
+sudo perf record -F 997 -g bins/$config/qps_json_driver --scenarios_json="$SCENARIOS_JSON_ARG"
+sudo perf report
+
diff --git a/tools/run_tests/filter_pull_request_tests.py b/tools/run_tests/filter_pull_request_tests.py
new file mode 100644
index 0000000..55dab42
--- /dev/null
+++ b/tools/run_tests/filter_pull_request_tests.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python2.7
+# 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.
+
+"""Filter out tests based on file differences compared to merge target branch"""
+
+import re
+from subprocess import call, check_output
+
+
+class TestSuite:
+  """
+  Contains tag to identify job as belonging to this test suite and
+  triggers to identify if changed files are relevant
+  """
+  def __init__(self, tags):
+    """
+    Build TestSuite to group tests by their tags
+    :param tag: string used to identify if a job belongs to this TestSuite
+    todo(mattkwong): Change the use of tag because do not want to depend on
+    job.shortname to identify what suite a test belongs to
+    """
+    self.triggers = []
+    self.tags = tags
+
+  def add_trigger(self, trigger):
+    """
+    Add a regex to list of triggers that determine if a changed file should run tests
+    :param trigger: regex matching file relevant to tests
+    """
+    self.triggers.append(trigger)
+
+# Create test suites
+_core_test_suite = TestSuite(['_c_'])
+_cpp_test_suite = TestSuite(['_c++_'])
+_csharp_test_suite = TestSuite(['_csharp_'])
+_node_test_suite = TestSuite(['_node_'])
+_objc_test_suite = TestSuite(['_objc_'])
+_php_test_suite = TestSuite(['_php_', '_php7_'])
+_python_test_suite = TestSuite(['_python_'])
+_ruby_test_suite = TestSuite(['_ruby'])
+_all_test_suites = [_core_test_suite, _cpp_test_suite, _csharp_test_suite,
+                    _node_test_suite, _objc_test_suite, _php_test_suite,
+                    _python_test_suite, _ruby_test_suite]
+
+# Dictionary of whitelistable files where the key is a regex matching changed files
+# and the value is a list of tests that should be run. An empty list means that
+# the changed files should not trigger any tests. Any changed file that does not
+# match any of these regexes will trigger all tests
+_WHITELIST_DICT = {
+  '^templates/.*': [],
+  '^doc/.*': [],
+  '^examples/.*': [],
+  '^summerofcode/.*': [],
+  '.*README.md$': [],
+  '.*LICENSE$': [],
+  '^src/cpp.*': [_cpp_test_suite],
+  '^src/csharp.*': [_csharp_test_suite],
+  '^src/node.*': [_node_test_suite],
+  '^src/objective-c.*': [_objc_test_suite],
+  '^src/php.*': [_php_test_suite],
+  '^src/python.*': [_python_test_suite],
+  '^src/ruby.*': [_ruby_test_suite],
+  '^test/core.*': [_core_test_suite],
+  '^test/cpp.*': [_cpp_test_suite],
+  '^test/distrib/cpp.*': [_cpp_test_suite],
+  '^test/distrib/csharp.*': [_csharp_test_suite],
+  '^test/distrib/node.*': [_node_test_suite],
+  '^test/distrib/php.*': [_php_test_suite],
+  '^test/distrib/python.*': [_python_test_suite],
+  '^test/distrib/ruby.*': [_ruby_test_suite]
+}
+# Add all triggers to their respective test suites
+for trigger, test_suites in _WHITELIST_DICT.iteritems():
+  for test_suite in test_suites:
+    test_suite.add_trigger(trigger)
+
+
+def _get_changed_files(base_branch):
+  """
+  Get list of changed files between current branch and base of target merge branch
+  """
+  # git fetch might need to be called on Jenkins slave
+  # todo(mattkwong): remove or uncomment below after seeing if Jenkins needs this
+  # call(['git', 'fetch'])
+
+  # Get file changes between branch and merge-base of specified branch
+  # Not combined to be Windows friendly
+  base_commit = check_output(["git", "merge-base", base_branch, "HEAD"]).rstrip()
+  return check_output(["git", "diff", base_commit, "--name-only"]).splitlines()
+
+
+def _can_skip_tests(file_names, triggers):
+  """
+  Determines if tests are skippable based on if all files do not match list of regexes
+  :param file_names: list of changed files generated by _get_changed_files()
+  :param triggers: list of regexes matching file name that indicates tests should be run
+  :return: safe to skip tests
+  """
+  for file_name in file_names:
+    if any(re.match(trigger, file_name) for trigger in triggers):
+      return False
+  return True
+
+
+def _remove_irrelevant_tests(tests, tag):
+  """
+  Filters out tests by config or language - will not remove sanitizer tests
+  :param tests: list of all tests generated by run_tests_matrix.py
+  :param tag: string representing language or config to filter - "_(language)_" or "_(config)"
+  :return: list of relevant tests
+  """
+  # todo(mattkwong): find a more reliable way to filter tests - don't use shortname
+  return [test for test in tests if tag not in test.shortname or
+          any(san_tag in test.shortname for san_tag in ['_asan', '_tsan', '_msan'])]
+
+
+def _remove_sanitizer_tests(tests):
+  """
+  Filters out sanitizer tests
+  :param tests: list of all tests generated by run_tests_matrix.py
+  :return: list of relevant tests
+  """
+  # todo(mattkwong): find a more reliable way to filter tests - don't use shortname
+  return [test for test in tests if
+          all(san_tag not in test.shortname for san_tag in ['_asan', '_tsan', '_msan'])]
+
+
+def filter_tests(tests, base_branch):
+  """
+  Filters out tests that are safe to ignore
+  :param tests: list of all tests generated by run_tests_matrix.py
+  :return: list of relevant tests
+  """
+  print("Finding file differences between %s repo and current branch...\n" % base_branch)
+  changed_files = _get_changed_files(base_branch)
+  for changed_file in changed_files:
+    print(changed_file)
+  print
+
+  # Regex that combines all keys in _WHITELIST_DICT
+  all_triggers = "(" + ")|(".join(_WHITELIST_DICT.keys()) + ")"
+  # Check if all tests have to be run
+  for changed_file in changed_files:
+    if not re.match(all_triggers, changed_file):
+      return(tests)
+  # Filter out tests by language
+  for test_suite in _all_test_suites:
+    if _can_skip_tests(changed_files, test_suite.triggers):
+      for tag in test_suite.tags:
+        print("  Filtering %s tests" % tag)
+        tests = _remove_irrelevant_tests(tests, tag)
+  # Sanitizer tests skipped if core and c++ are skipped
+  if _can_skip_tests(changed_files, _cpp_test_suite.triggers + _core_test_suite.triggers):
+    print("  Filtering Sanitizer tests")
+    tests = _remove_sanitizer_tests(tests)
+
+  return tests
diff --git a/tools/run_tests/performance/build_performance.sh b/tools/run_tests/performance/build_performance.sh
index 352c679..e981cae 100755
--- a/tools/run_tests/performance/build_performance.sh
+++ b/tools/run_tests/performance/build_performance.sh
@@ -54,6 +54,9 @@
   "go")
     tools/run_tests/performance/build_performance_go.sh
     ;;
+  "csharp")
+    tools/run_tests/run_tests.py -l $language -c $CONFIG --build_only -j 8 --compiler coreclr
+    ;;
   *)
     tools/run_tests/run_tests.py -l $language -c $CONFIG --build_only -j 8
     ;;
diff --git a/tools/run_tests/performance/kill_workers.sh b/tools/run_tests/performance/kill_workers.sh
index 279cc7d..6e33efa 100755
--- a/tools/run_tests/performance/kill_workers.sh
+++ b/tools/run_tests/performance/kill_workers.sh
@@ -40,6 +40,7 @@
 
 # C#
 ps -C mono -o pid=,cmd= | grep QpsWorker | awk '{print $1}' | xargs kill -9 || true
+ps -C dotnet -o pid=,cmd= | grep QpsWorker | awk '{print $1}' | xargs kill -9 || true
 
 # Ruby
 ps -C ruby -o pid=,cmd= | grep 'qps/worker.rb' | awk '{print $1}' | xargs kill -9 || true
diff --git a/tools/run_tests/performance/run_worker_csharp.sh b/tools/run_tests/performance/run_worker_csharp.sh
index b91df09..b4c5ec2 100755
--- a/tools/run_tests/performance/run_worker_csharp.sh
+++ b/tools/run_tests/performance/run_worker_csharp.sh
@@ -33,6 +33,6 @@
 cd $(dirname $0)/../../..
 
 # needed to correctly locate testca
-cd src/csharp/Grpc.IntegrationTesting.QpsWorker/bin/Release
+cd src/csharp/Grpc.IntegrationTesting.QpsWorker/bin/Release/netcoreapp1.0
 
-mono Grpc.IntegrationTesting.QpsWorker.exe $@
+dotnet exec Grpc.IntegrationTesting.QpsWorker.dll $@
diff --git a/tools/run_tests/run_interop_tests.py b/tools/run_tests/run_interop_tests.py
index 389d070..aa246f2 100755
--- a/tools/run_tests/run_interop_tests.py
+++ b/tools/run_tests/run_interop_tests.py
@@ -64,8 +64,9 @@
 
 _SKIP_COMPRESSION = _SKIP_CLIENT_COMPRESSION + _SKIP_SERVER_COMPRESSION
 
-_SKIP_ADVANCED = ['custom_metadata', 'status_code_and_message',
-                  'unimplemented_method']
+_SKIP_ADVANCED_CXX_AND_GO = ['custom_metadata', 'unimplemented_method']
+
+_SKIP_ADVANCED = _SKIP_ADVANCED_CXX_AND_GO + ['status_code_and_message']
 
 _TEST_TIMEOUT = 3*60
 
@@ -89,10 +90,10 @@
     return {}
 
   def unimplemented_test_cases(self):
-    return _SKIP_ADVANCED
+    return _SKIP_ADVANCED_CXX_AND_GO
 
   def unimplemented_test_cases_server(self):
-    return _SKIP_ADVANCED
+    return _SKIP_ADVANCED_CXX_AND_GO
 
   def __str__(self):
     return 'c++'
@@ -206,10 +207,10 @@
     return {}
 
   def unimplemented_test_cases(self):
-    return _SKIP_ADVANCED + _SKIP_COMPRESSION
+    return _SKIP_ADVANCED_CXX_AND_GO + _SKIP_COMPRESSION
 
   def unimplemented_test_cases_server(self):
-    return _SKIP_ADVANCED + _SKIP_COMPRESSION
+    return _SKIP_ADVANCED_CXX_AND_GO + _SKIP_COMPRESSION
 
   def __str__(self):
     return 'go'
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index dd070b1..e9ee5f5 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -69,7 +69,7 @@
 
 
 _POLLING_STRATEGIES = {
-  'linux': ['epoll', 'poll', 'legacy']
+  'linux': ['epoll', 'poll', 'poll-cv', 'legacy']
 }
 
 
diff --git a/tools/run_tests/run_tests_matrix.py b/tools/run_tests/run_tests_matrix.py
index 60c21a1..550dd10 100755
--- a/tools/run_tests/run_tests_matrix.py
+++ b/tools/run_tests/run_tests_matrix.py
@@ -36,13 +36,14 @@
 import os
 import report_utils
 import sys
+from filter_pull_request_tests import filter_tests
 
 _ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
 os.chdir(_ROOT)
 
 # Set the timeout high to allow enough time for sanitizers and pre-building
 # clang docker.
-_RUNTESTS_TIMEOUT = 2*60*60
+_RUNTESTS_TIMEOUT = 4*60*60
 
 # Number of jobs assigned to each run_tests.py instance
 _INNER_JOBS = 2
@@ -231,6 +232,15 @@
                   action='store_const',
                   const=True,
                   help='Only print what would be run.')
+argp.add_argument('--filter_pr_tests',
+	          default=False,
+		  action='store_const',	
+		  const=True,	  
+		  help='Filters out tests irrelavant to pull request changes.')
+argp.add_argument('--base_branch',
+                  default='origin/master',
+                  type=str,
+                  help='Branch that pull request is requesting to merge into')
 args = argp.parse_args()
 
 extra_args = []
@@ -264,6 +274,19 @@
     print '  %s' % job.shortname
 print
 
+if args.filter_pr_tests:
+  print 'IMPORTANT: Test filtering is not active; this is only for testing.'
+  relevant_jobs = filter_tests(jobs, args.base_branch)
+  # todo(mattkwong): add skipped tests to report.xml
+  print
+  if len(relevant_jobs) == len(jobs):
+    print '(TESTING) No tests will be skipped.'
+  else:
+    print '(TESTING) These tests will be skipped:'
+    for job in list(set(jobs) - set(relevant_jobs)):
+      print '  %s' % job.shortname
+  print
+
 if args.dry_run:
   print '--dry_run was used, exiting'
   sys.exit(1)
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index b11b579..4122dc0 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -2084,6 +2084,23 @@
       "gpr", 
       "gpr_test_util", 
       "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c", 
+    "name": "wakeup_fd_cv_test", 
+    "src": [
+      "test/core/iomgr/wakeup_fd_cv_test.c"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
       "grpc++", 
       "grpc++_test_util", 
       "grpc_test_util"
@@ -2978,6 +2995,25 @@
       "gpr_test_util", 
       "grpc", 
       "grpc++", 
+      "grpc++_test", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "server_context_test_spouse_test", 
+    "src": [
+      "test/cpp/test/server_context_test_spouse_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc++", 
       "grpc++_test_util", 
       "grpc_test_util"
     ], 
@@ -4930,6 +4966,23 @@
     "type": "lib"
   }, 
   {
+    "deps": [
+      "grpc++"
+    ], 
+    "headers": [
+      "include/grpc++/test/server_context_test_spouse.h"
+    ], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "grpc++_test", 
+    "src": [
+      "include/grpc++/test/server_context_test_spouse.h", 
+      "src/cpp/test/server_context_test_spouse.cc"
+    ], 
+    "third_party": false, 
+    "type": "lib"
+  }, 
+  {
     "deps": [], 
     "headers": [
       "test/cpp/util/test_config.h"
@@ -6405,10 +6458,10 @@
       "src/core/lib/iomgr/timer_heap.h", 
       "src/core/lib/iomgr/udp_server.h", 
       "src/core/lib/iomgr/unix_sockets_posix.h", 
+      "src/core/lib/iomgr/wakeup_fd_cv.h", 
       "src/core/lib/iomgr/wakeup_fd_pipe.h", 
       "src/core/lib/iomgr/wakeup_fd_posix.h", 
       "src/core/lib/iomgr/workqueue.h", 
-      "src/core/lib/iomgr/workqueue_posix.h", 
       "src/core/lib/iomgr/workqueue_windows.h", 
       "src/core/lib/json/json.h", 
       "src/core/lib/json/json_common.h", 
@@ -6556,6 +6609,8 @@
       "src/core/lib/iomgr/unix_sockets_posix.c", 
       "src/core/lib/iomgr/unix_sockets_posix.h", 
       "src/core/lib/iomgr/unix_sockets_posix_noop.c", 
+      "src/core/lib/iomgr/wakeup_fd_cv.c", 
+      "src/core/lib/iomgr/wakeup_fd_cv.h", 
       "src/core/lib/iomgr/wakeup_fd_eventfd.c", 
       "src/core/lib/iomgr/wakeup_fd_nospecial.c", 
       "src/core/lib/iomgr/wakeup_fd_pipe.c", 
@@ -6563,8 +6618,6 @@
       "src/core/lib/iomgr/wakeup_fd_posix.c", 
       "src/core/lib/iomgr/wakeup_fd_posix.h", 
       "src/core/lib/iomgr/workqueue.h", 
-      "src/core/lib/iomgr/workqueue_posix.c", 
-      "src/core/lib/iomgr/workqueue_posix.h", 
       "src/core/lib/iomgr/workqueue_windows.c", 
       "src/core/lib/iomgr/workqueue_windows.h", 
       "src/core/lib/json/json.c", 
diff --git a/tools/run_tests/tests.json b/tools/run_tests/tests.json
index c339506..4095599 100644
--- a/tools/run_tests/tests.json
+++ b/tools/run_tests/tests.json
@@ -2066,6 +2066,25 @@
     "ci_platforms": [
       "linux", 
       "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c", 
+    "name": "wakeup_fd_cv_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
+  {
+    "args": [], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
       "posix", 
       "windows"
     ], 
@@ -2646,6 +2665,27 @@
     "ci_platforms": [
       "linux", 
       "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "gtest": true, 
+    "language": "c++", 
+    "name": "server_context_test_spouse_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ]
+  }, 
+  {
+    "args": [], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
       "posix"
     ], 
     "cpu_cost": 0.1, 
@@ -57992,6 +58032,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/0083d5addbeca55271ed7ef93c8016bf7ca76903"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/00c8446b230bebbae2b473552b174a06b446337a"
     ], 
     "ci_platforms": [
@@ -58220,6 +58279,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/07b0bed3226eefac4a84000ec584e4ce06ebf1bf"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/07cec5c8d9c856a910c6fb57da2ae954f44beed0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/08a8a647b6a8f47ae10852322d14832fc15021f1"
     ], 
     "ci_platforms": [
@@ -58277,6 +58374,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/0c27c9999302b39bf2256a90b0cdb767fb2b6fe3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/0c30868720d5e1a19ff23c53740749c37a43540d"
     ], 
     "ci_platforms": [
@@ -58353,6 +58469,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/0d407f099f8418de3dd94bd2146c858a8c6575ad"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/0d4d486aa9fd6e9c10cc9ca8967e922cadddb2fe"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/0d8c547f1d261ba07c2648bae009636c17709600"
     ], 
     "ci_platforms": [
@@ -58372,6 +58526,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/0d9ba07b57eb0e076b187c4455f662db085e730b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/0dd33527db106a3e84172e8f2189734b00ced4ed"
     ], 
     "ci_platforms": [
@@ -58429,6 +58602,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/0f6b989cec08ef9da603dc83704d85900bd22f1f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/0f83cbec19c834f534f353f4fce20c0cd88231f5"
     ], 
     "ci_platforms": [
@@ -58524,6 +58716,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/10b25b0726cb6d820165699e5a453691c7a9c343"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/10f5d1937cb068fee7f85e2654be2bfe77498bb9"
     ], 
     "ci_platforms": [
@@ -58600,6 +58811,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/1231c6d007d9e43d169122348363e20d9f25ee93"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/1306c4c6ea714d4db0e4d814c944d8d40335e0fa"
     ], 
     "ci_platforms": [
@@ -58619,6 +58849,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/13a9b61e431c20734c19bb36d85883b6a501284e"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/1402bbcac6fa24eeb0475250e33f704096e2fb45"
     ], 
     "ci_platforms": [
@@ -58733,6 +58982,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/1698ec182fad9d973b84615da3a683ecdf2d0b3b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/169f579e66b4b8ff423891a40380e648e8d45247"
     ], 
     "ci_platforms": [
@@ -58790,6 +59058,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/1859e2ee759e20fe195f67615a1576ce2b7d5bbd"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/1875a4acdcffe505ca92ea8af8d9d6b174736e80"
     ], 
     "ci_platforms": [
@@ -58885,6 +59172,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/1a9017db5ad8a9dc6cfe72305da1683a87a73452"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/1aee32faadffa3c2ec508e8fd30006423665488f"
     ], 
     "ci_platforms": [
@@ -58923,6 +59229,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/1bd90335afc9e0a1e6a9296e3cc27c03c1201886"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/1be157b0fc79f0e7e1e05dfa3cbbe1ad71528bc2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/1c222dae4e2cde1fca9f9bf6226200f70d625342"
     ], 
     "ci_platforms": [
@@ -59284,6 +59628,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/2185f411bdb1edc610f16ffc86836ae366193e03"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/21a6a133f3d1e06c077032ba56a7df4161f62efe"
     ], 
     "ci_platforms": [
@@ -59322,6 +59685,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/22661803bd1c7198df4be6e08924ef6a48af9cd4"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/230527b90b0179139c961aca426187893191fdf2"
     ], 
     "ci_platforms": [
@@ -59569,6 +59951,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/2717067bbc0e9bfc1d90d15cddf6154800a25ec6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/27f37037525aac7a41ffbadd6ce52e5a1851a2b7"
     ], 
     "ci_platforms": [
@@ -59588,6 +59989,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/2825cfc19c9371f4fe70851283c68d49470d4d55"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/2862adc802092f1a422416a1666a5142f71d5d7f"
     ], 
     "ci_platforms": [
@@ -59683,6 +60103,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/29303c16f3afa18c2c0b84e77e587535a705a74c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/299034b9e0cc8d91c049c489dca6d1a2b8b08959"
     ], 
     "ci_platforms": [
@@ -59797,6 +60236,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/2b5eb5aac77af905877bd98ec2c4d746b247abb6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/2b71439e9ebf611a92386b9f21ad44bde7926184"
     ], 
     "ci_platforms": [
@@ -59911,6 +60369,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/2cc43573f271ecd332551c1fb34ebc8645eaefe8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/2d83097b3cbd2245b085e749fe923fb590790e0c"
     ], 
     "ci_platforms": [
@@ -60025,6 +60502,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/2feb41037f5dd34e9f3465a2fbf1a6d355c8ce9d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/300998021c7f743ff49d9cc192343ffd43eb47f2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/301e10bb6d9f60d91efde4e0c48893203a5b8b88"
     ], 
     "ci_platforms": [
@@ -60063,6 +60578,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/310b2aff5e2ec78b6004630bed39d49f8d13bb21"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/3128887b8e02f1873ed6b36766a870543269ea00"
     ], 
     "ci_platforms": [
@@ -60443,6 +60977,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/3bb052abecc1b916cc869b9aad29c9dd55a95068"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/3c01b1f89d50fa37fcb3457cd3dd6502fe84e25b"
     ], 
     "ci_platforms": [
@@ -60462,6 +61015,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/3c5fa483ebfabe6e684831ce7c413176bc998c33"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/3c6444b64ace5cd6c145614ad4412382271a6120"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/3c7b516e302ad3503a933b5dcfb8c58acaea07a0"
     ], 
     "ci_platforms": [
@@ -60576,6 +61167,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/4045d25f065bb1d70a8b9c3751f7453d4b0625b9"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/407cedf992b14edac6e19f7d440ab73c88e72465"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/4097094277bc09981f428280fc0cc0f590f20ded"
     ], 
     "ci_platforms": [
@@ -60595,6 +61224,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/411966ea7d9164fc432eeab55a55248ad808bb01"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/415dde26637ed3c0e803111c532a1a9ea9c49092"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/41aad2f11a7ab418213352e84de872d9997db8d2"
     ], 
     "ci_platforms": [
@@ -61146,6 +61813,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/4fc34239f220392581520aa8cebc659daa65a7a6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/50ece7ea16659b4e1a2284cea963fab662c19e6b"
     ], 
     "ci_platforms": [
@@ -61203,6 +61889,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/52939682304314f04897deddfbc9c7afa8ee50a9"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/52c00bde7f4af95a86deb0a6717d1faf2828a939"
     ], 
     "ci_platforms": [
@@ -61260,6 +61965,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/5369926a559827d08bccf264876d592c7cae660d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/53ef530f65b0cff2e338a51b469c224f53b628d7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/542c958c84d1e319b9ba23c52de2c4bca08a8dc7"
     ], 
     "ci_platforms": [
@@ -61355,6 +62098,63 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/560c1057487e6b0d2d457748c3ad8434423eb263"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/564f203f678fb333c7b1f8f4df79237589ce346d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/56b0ac0636c57838f63415082b3ae2ec7a93f017"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/570ca8d2555dde94aa3b3121e8f5256e83eabe5e"
     ], 
     "ci_platforms": [
@@ -61431,6 +62231,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/58bcbd601894835bb3312d2a0bc56f2e0f65984c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/58d6dffb65a1fe1bc4e3fa970a15459587a32f77"
     ], 
     "ci_platforms": [
@@ -61469,6 +62288,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/598d346f284bcff26d1de997c4ba5c4794c90b68"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/59d28886db21f371ac9d999b68b116bcf425d971"
     ], 
     "ci_platforms": [
@@ -61526,6 +62364,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/5c14b48da74ab06b3cc20c4fe355e24f7dd7852a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/5ca233a53e3e425cc12e04b466a49789291eaa00"
     ], 
     "ci_platforms": [
@@ -61564,6 +62421,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/5de72e607205dc17a45df703ec4e9b63c36821ec"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/5e1659e7cd840ab3f958273ebffdd215f2c81da6"
     ], 
     "ci_platforms": [
@@ -61583,6 +62459,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/5e25cf639ba8ea37543d944f5efa94824c6272ff"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/5f247d7b6753f7a8798cf952f49f303c532e017c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/605e474e9d9436488dfe084d348908e4dfab81a3"
     ], 
     "ci_platforms": [
@@ -61697,6 +62611,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/63a1cb41d219394c9bab947202921506f3574ad0"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/64696e93ead18265cdac3fb37dae29ad3be6d764"
     ], 
     "ci_platforms": [
@@ -61773,6 +62706,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/650f74738d3961af2d1fe85ad8fc8212ea13cbbf"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/653ec14661c40ea25bdbab4a7cb9371c669d10d9"
     ], 
     "ci_platforms": [
@@ -61792,6 +62744,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/65dff388749da6a44926b491cdc555f61d708171"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/66145518601b1405361df12570f6e0b2b9a2e5b3"
     ], 
     "ci_platforms": [
@@ -61868,6 +62839,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/676adbb1e5b3f4f9e3cba51d3d4ef963ba4ea7e3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/67e72cea2b7042f08e8dfba5191d27bb390e4d00"
     ], 
     "ci_platforms": [
@@ -61887,6 +62877,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/67f160446ded73c408f4e5a0665731b642b6edd4"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/6856c7cb02d2ba74a60fd47140f042701dda63b3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/685fbddd9ea612b25e325a50bd659997b4d77da1"
     ], 
     "ci_platforms": [
@@ -61963,6 +62991,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/69e14b73af03e8f2d998cfcf16215f65bf589efb"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/69e52eef5dd0c51012b5c974cf70f4074ba814a9"
     ], 
     "ci_platforms": [
@@ -62248,6 +63295,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/77cff7548cafe87410e4a0dde3ba6892b25594d3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/77ea9180617391d8503427a1c060538182f7729f"
     ], 
     "ci_platforms": [
@@ -62324,6 +63390,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/7beeb19272131701f3a0d1dd633f1b1969899366"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/7c2e48b0d08aaeb95b5ca26036384aa2cec9de77"
     ], 
     "ci_platforms": [
@@ -62780,6 +63865,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/8b0cf53ac17015fe066002cb3814933df9ee96be"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/8b5c4543923da5e468aca1de1ab880aed2ac4451"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/8b7b914723bfc23ec650cb91d209141641fba09f"
     ], 
     "ci_platforms": [
@@ -62913,6 +64036,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/8d9784f85e9662734e180ca8bec2164425ae8a87"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/8dfc4e78007040009f37109f9ca928c31b3ebb49"
     ], 
     "ci_platforms": [
@@ -62932,6 +64074,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/8e3f138d163022d6e105ab595788f4cfdd9b9db3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/8ea624983d766ed45780378a3eec24eb2faeb229"
     ], 
     "ci_platforms": [
@@ -63046,6 +64207,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/914464d372dcccf31ed5331293d84121e17616bb"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/91916df7c8f04d8c2b6b8f4aeaeee6972ce0de74"
     ], 
     "ci_platforms": [
@@ -63122,6 +64302,63 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/934a41b5027d1c5cca27ebda57560c38cb9e09ea"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/9354652806d96b09c8e7082b1b7d22e7c3fb9f0b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/9398ac1c2b4015792661266a9c84b6d7a68c3155"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/93ac93b7deabdfb4f86eb37a1e9f6669957d14a6"
     ], 
     "ci_platforms": [
@@ -63312,6 +64549,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/99099024a3f3e389f57cb7b697eb34485846f316"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/998a54dc94ab6e7d6a6066415fb0dd9b52356171"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/999d0995c2f09beda8783eac95d7643a11d5c89a"
     ], 
     "ci_platforms": [
@@ -63692,6 +64967,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/a25b31398669b585ccab97bceadc31994de7ead7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/a29a547671badd3154789e1a02bdb87332fcd6a4"
     ], 
     "ci_platforms": [
@@ -63749,6 +65043,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/a39ac9e92b41d1889096ed415b4c2eb1aba6ed50"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/a3cd54d43d3b3bdfcf224d636dc11ce1b5ee4d30"
     ], 
     "ci_platforms": [
@@ -63844,6 +65157,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/a5c2fdae1a1c0487d00db0eec6e3429b12244b1f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/a5cf80b996b2ba8c9580f8ecd22720c48de41044"
     ], 
     "ci_platforms": [
@@ -63882,6 +65214,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/a649093880c2a2f143f861893eaff5d30be95eb7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/a6603e797695274d10bce000f66ca0a715f7d8c0"
     ], 
     "ci_platforms": [
@@ -63977,6 +65328,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/a8249ebfe91327806446f14a6b2e7d9c8440257f"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/a8e306820fb76566b522c23ec68bdce0ad0536f2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/a8e67676784506d2e6eab3a0dfa25e53a80b40a0"
     ], 
     "ci_platforms": [
@@ -63996,6 +65385,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/a97dbb159ef9bc6e39c9c25e04315752e871e739"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/a9d71e1ff2912d8874e38fc61cbd9a8ef28af4a9"
     ], 
     "ci_platforms": [
@@ -64186,6 +65594,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/ae448bfe17f9a3a6eff074d4caa9f7261c94d2d5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/ae8c538d4ad7f2996ac724bad7a075e1aea32556"
     ], 
     "ci_platforms": [
@@ -64205,6 +65632,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/ae8cdc02275a1436bc131bee52a17ee797e2e6c9"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/aef36c49d7dec0dcf8cdc224d9e9221fa2cb1db0"
     ], 
     "ci_platforms": [
@@ -64300,6 +65746,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/b10353c265bef989d8909055fd6cd52e49eef3e6"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/b24a0dd1bc0bfabb832f0d1c8410c018c4ddaf4e"
     ], 
     "ci_platforms": [
@@ -64414,6 +65879,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/b387e46c23912785e6c353ab49b8ea4a92c2c2e5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/b39bfaf6a3072d8a50984dcc54967e9246f8d3e5"
     ], 
     "ci_platforms": [
@@ -64433,6 +65917,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/b3cfcd55b0331ab0c931b8c61d4df41464587f10"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/b3f33b78433af7f607bc99b569b0cef95a1a6ca0"
     ], 
     "ci_platforms": [
@@ -64509,6 +66012,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/b758f5c019696f33c50895168219c0e6cb04e11d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/b7b664a39372dd6142b8ef7906857e4ab3f1fc84"
     ], 
     "ci_platforms": [
@@ -64547,6 +66069,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/b93fd0a15287dd035eac86e547e3ce42183bdb28"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/ba3566735888b53712c6b2e6d52ff5f2197afd6a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/ba942f8fb244b60561a067129c242c4bc4fdd5e1"
     ], 
     "ci_platforms": [
@@ -64680,6 +66240,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/bd275178fd473028a5cedf7d5780b27e809882ee"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/be3237e72b3d8d56eec0520145dd7d1a5064eede"
     ], 
     "ci_platforms": [
@@ -64832,6 +66411,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/c4c53b4727e9e1f040c5d7870639dd3daa184ddb"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/c4e60ae7c05b12a90dd7c43fbc85ae4be7540f18"
     ], 
     "ci_platforms": [
@@ -65022,6 +66620,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/c9e2cf8be8a4dc2294020026c62840ef1fb4853b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/ca0db313bf949ba3f87a5254646a7a7dc8a7f89d"
     ], 
     "ci_platforms": [
@@ -65041,6 +66658,63 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/caaf9a7751c0eccc34f0fc00a048012ab5ed2f37"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/cb49955601d171fd14c9ac21137b221392c7dab1"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/cbaabef34763f2fd922e67ff5f2ea283347e9823"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/cbe59c62c6d36c7307c438159327e320cd2fcf57"
     ], 
     "ci_platforms": [
@@ -65136,6 +66810,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/cdd1a4e358ee2396ece54b32c1f0a8d0a2e3f3dc"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/ce1c326f3b0147841550ce3b5126390764bae8e8"
     ], 
     "ci_platforms": [
@@ -65231,6 +66924,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/cf922d44bf08d223d3ebcd37a7e77d3e43555d08"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/cfa40fccc5ea4304e83ca26f4e567765c2c08627"
     ], 
     "ci_platforms": [
@@ -65288,6 +67000,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/crash-17d5b79ce495f7d3f2e33d95588457281a5e8965"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/crash-3bd02c98286bfa7be8e13c5500ddb587bba74fbb"
     ], 
     "ci_platforms": [
@@ -65307,6 +67038,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/crash-3e52af52deb0ed5d6ef06487d7e475e8fb616972"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/crash-53e93a1906d8442d058500e7107929cdd3e84ff8"
     ], 
     "ci_platforms": [
@@ -65421,6 +67171,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/crash-ff53a3d713e83ae945b8dd1782e21f5b51aa649a"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/d17e9507af1855fcf9eca78e2d25c8fb2c40a34c"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/d194592e6f471dd487ca2625e6c3da7802ea372f"
     ], 
     "ci_platforms": [
@@ -65630,6 +67418,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/d46c3dcede830286dd9f4a1ba02a20a0b1430664"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/d49450b97f489f0dea74a9f83c71abeba1066d3c"
     ], 
     "ci_platforms": [
@@ -65668,6 +67475,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/d4a744ef6dcef5cf08d5289e167b26270d39e9f2"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/d5a85ad91cfde27a96960b2e783d2ee43c50dcb9"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/d60469c0b5b385f20d55aa5cca55bc2c801f3b95"
     ], 
     "ci_platforms": [
@@ -65744,6 +67589,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/d88bb0b7ff687af84f33e6af22d3516fcdac5534"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/d89026894e6c5f8b5c88dec12950f56c4b6924ba"
     ], 
     "ci_platforms": [
@@ -65763,6 +67627,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/d895ece988ad4712b87de8aa9bc273eee315e8b8"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/d90c312791129dee8c5f85cb3308323d0c39b70d"
     ], 
     "ci_platforms": [
@@ -65820,6 +67703,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/da424090e1b94c5d0e91e26f3f3dd6c4af18fcd5"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/da4d300d0a8e6f803ec053e3e7689c4b91eaef90"
     ], 
     "ci_platforms": [
@@ -65896,6 +67798,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/db3a30a6d8e605dd587e51b214c42f68bc43cf19"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/dc4a248fa4c903ce3a571dd18aea575019445740"
     ], 
     "ci_platforms": [
@@ -66200,6 +68121,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/e3d12a2385b75443fe38d989e77c252e1f3cdb6d"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/e40b0fa5d814be8f2081ca2c8e0a4090d4893831"
     ], 
     "ci_platforms": [
@@ -66276,6 +68216,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/e4f55281c481484bd9edc28fd10df0c2e0f7d546"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/e5a7c086208248a15ee6fa5195fc4ce22469de15"
     ], 
     "ci_platforms": [
@@ -66371,6 +68330,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/e6c52f2f31db7595d1ecde2939a7390777f15182"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/e6f5cc0702a5f38b9e7339849e1dd2e4001e547d"
     ], 
     "ci_platforms": [
@@ -66675,6 +68653,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f09cd3e3a16658174717668e51e7382e491df1da"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f11abb090bae8cdac1f7d9a2e344f2def0e50066"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/f1b9b6803e41beabb1a762d511fc148116e09e78"
     ], 
     "ci_platforms": [
@@ -66770,6 +68786,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f4ae2a2b692bfa83cdde75d007813426e14daef7"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f5a629c8fd5720236b66a875e96ea22e29c45965"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/f5b1eab444efb2664a295d4e6d087eb209c0c480"
     ], 
     "ci_platforms": [
@@ -66789,6 +68843,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f6627c55881fe4f0c8e6999980fb226836e6f5ce"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/f66305230042fa83fcd1b98c469d90ffef3ff6da"
     ], 
     "ci_platforms": [
@@ -66865,6 +68938,44 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f7aeceaf0b6d971038a677994b5d080fa0e18011"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f803c87a92662898e2c8c847787b56d2c31f63b3"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/f8467d9574de94b9bb904f75a6a7e2405c36f105"
     ], 
     "ci_platforms": [
@@ -66903,6 +69014,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f89ad475ff51a5a9fe18603df833453bed320f36"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/f8fb1348ec3ceeb75c2a03df6a2ead0de6f4127a"
     ], 
     "ci_platforms": [
@@ -66941,6 +69071,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/f9583b3a39c1aecbba6e81d71e7fe9b9519c8b08"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/f96843fdf2d6fdd661c26201d96ae7bec72c6c3d"
     ], 
     "ci_platforms": [
@@ -67150,6 +69299,25 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/client_fuzzer_corpus/ffd52d31f9c59a346aa195a683f077dda5ecef6b"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "client_fuzzer_one_entry", 
+    "platforms": [
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/client_fuzzer_corpus/hdr_frame.bin"
     ], 
     "ci_platforms": [
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 965d06d..adfebda 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -350,10 +350,10 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\timer_heap.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\udp_server.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_pipe.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue.h" />
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\json\json.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\json\json_common.h" />
@@ -575,6 +575,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix_noop.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_eventfd.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_nospecial.c">
@@ -583,8 +585,6 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.c">
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.c">
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\json\json.c">
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index dddd4ec..ee5bf36 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -172,6 +172,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix_noop.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.c">
+      <Filter>src\core\lib\iomgr</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_eventfd.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
@@ -184,9 +187,6 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.c">
-      <Filter>src\core\lib\iomgr</Filter>
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
@@ -833,6 +833,9 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.h">
+      <Filter>src\core\lib\iomgr</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_pipe.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
@@ -842,9 +845,6 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.h">
-      <Filter>src\core\lib\iomgr</Filter>
-    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj
index b724c21..382494a 100644
--- a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj
+++ b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj
@@ -243,10 +243,10 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\timer_heap.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\udp_server.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_pipe.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue.h" />
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\json\json.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\json\json_common.h" />
@@ -423,6 +423,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix_noop.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_eventfd.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_nospecial.c">
@@ -431,8 +433,6 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.c">
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.c">
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\json\json.c">
diff --git a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters
index 92806fa..2f7854d 100644
--- a/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_test_util/grpc_test_util.vcxproj.filters
@@ -226,6 +226,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix_noop.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.c">
+      <Filter>src\core\lib\iomgr</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_eventfd.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
@@ -238,9 +241,6 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.c">
-      <Filter>src\core\lib\iomgr</Filter>
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
@@ -620,6 +620,9 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.h">
+      <Filter>src\core\lib\iomgr</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_pipe.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
@@ -629,9 +632,6 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.h">
-      <Filter>src\core\lib\iomgr</Filter>
-    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index b467736..2b5613a 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -340,10 +340,10 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\timer_heap.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\udp_server.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix.h" />
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_pipe.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue.h" />
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\json\json.h" />
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\json\json_common.h" />
@@ -543,6 +543,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix_noop.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_eventfd.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_nospecial.c">
@@ -551,8 +553,6 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.c">
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.c">
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\json\json.c">
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index 6ffc1ea..e52425a 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -175,6 +175,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix_noop.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.c">
+      <Filter>src\core\lib\iomgr</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_eventfd.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
@@ -187,9 +190,6 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_posix.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
-    <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.c">
-      <Filter>src\core\lib\iomgr</Filter>
-    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.c">
       <Filter>src\core\lib\iomgr</Filter>
     </ClCompile>
@@ -743,6 +743,9 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\unix_sockets_posix.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
+    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_cv.h">
+      <Filter>src\core\lib\iomgr</Filter>
+    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\wakeup_fd_pipe.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
@@ -752,9 +755,6 @@
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
-    <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_posix.h">
-      <Filter>src\core\lib\iomgr</Filter>
-    </ClInclude>
     <ClInclude Include="$(SolutionDir)\..\src\core\lib\iomgr\workqueue_windows.h">
       <Filter>src\core\lib\iomgr</Filter>
     </ClInclude>
diff --git a/vsprojects/vcxproj/test/grpc++_test/grpc++_test.vcxproj b/vsprojects/vcxproj/test/grpc++_test/grpc++_test.vcxproj
new file mode 100644
index 0000000..995d60d
--- /dev/null
+++ b/vsprojects/vcxproj/test/grpc++_test/grpc++_test.vcxproj
@@ -0,0 +1,195 @@
+<?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>{3D3EAEA9-76C4-0CFE-4718-5A1F6B7F72C8}</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\cpptest.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\global.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\openssl.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\protobuf.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\winsock.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\zlib.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <TargetName>grpc++_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>grpc++_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>Windows</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>Windows</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>Windows</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>Windows</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+
+  <ItemGroup>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc++\test\server_context_test_spouse.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\src\cpp\test\server_context_test_spouse.cc">
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc++\grpc++.vcxproj">
+      <Project>{C187A093-A0FE-489D-A40A-6E33DE0F9FEB}</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/grpc++_test/grpc++_test.vcxproj.filters b/vsprojects/vcxproj/test/grpc++_test/grpc++_test.vcxproj.filters
new file mode 100644
index 0000000..e1d8fe1
--- /dev/null
+++ b/vsprojects/vcxproj/test/grpc++_test/grpc++_test.vcxproj.filters
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ClCompile Include="$(SolutionDir)\..\src\cpp\test\server_context_test_spouse.cc">
+      <Filter>src\cpp\test</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="$(SolutionDir)\..\include\grpc++\test\server_context_test_spouse.h">
+      <Filter>include\grpc++\test</Filter>
+    </ClInclude>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Filter Include="include">
+      <UniqueIdentifier>{418e2be9-9a04-af8a-8878-956543c507a1}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="include\grpc++">
+      <UniqueIdentifier>{bcaf3a2d-e884-bfbd-477b-09634054bcc5}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="include\grpc++\test">
+      <UniqueIdentifier>{3a14b66f-1d31-2d06-e9a1-c6d1199ab04d}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="src">
+      <UniqueIdentifier>{bc08c75b-ee7e-85a0-fb0f-7586bbd33c6f}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="src\cpp">
+      <UniqueIdentifier>{9797a57d-93ac-ff9c-5ac6-fd335777d782}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="src\cpp\test">
+      <UniqueIdentifier>{58d4b4e0-5617-abf4-48a6-f1264d127b27}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>
+
diff --git a/vsprojects/vcxproj/test/server_context_test_spouse_test/server_context_test_spouse_test.vcxproj b/vsprojects/vcxproj/test/server_context_test_spouse_test/server_context_test_spouse_test.vcxproj
new file mode 100644
index 0000000..8ba7432
--- /dev/null
+++ b/vsprojects/vcxproj/test/server_context_test_spouse_test/server_context_test_spouse_test.vcxproj
@@ -0,0 +1,207 @@
+<?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>{A6F63996-B33B-9904-57AB-9C9559D52007}</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\cpptest.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\global.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\openssl.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\protobuf.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\winsock.props" />
+    <Import Project="$(SolutionDir)\..\vsprojects\zlib.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <TargetName>server_context_test_spouse_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>server_context_test_spouse_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\cpp\test\server_context_test_spouse_test.cc">
+    </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\test\grpc++_test\grpc++_test.vcxproj">
+      <Project>{3D3EAEA9-76C4-0CFE-4718-5A1F6B7F72C8}</Project>
+    </ProjectReference>
+    <ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc++\grpc++.vcxproj">
+      <Project>{C187A093-A0FE-489D-A40A-6E33DE0F9FEB}</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/server_context_test_spouse_test/server_context_test_spouse_test.vcxproj.filters b/vsprojects/vcxproj/test/server_context_test_spouse_test/server_context_test_spouse_test.vcxproj.filters
new file mode 100644
index 0000000..469ef95
--- /dev/null
+++ b/vsprojects/vcxproj/test/server_context_test_spouse_test/server_context_test_spouse_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\cpp\test\server_context_test_spouse_test.cc">
+      <Filter>test\cpp\test</Filter>
+    </ClCompile>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Filter Include="test">
+      <UniqueIdentifier>{fa83809b-e1fe-3f85-ebbb-73570c33fa2e}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\cpp">
+      <UniqueIdentifier>{7bad6fd3-6c15-e1fa-4037-8627e3622c7c}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\cpp\test">
+      <UniqueIdentifier>{990725da-41e1-2c80-ecc7-2b90c8914dd7}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>
+