Merge pull request #13642 from markdroth/server_connection_timeout_1.7

Apply #13336 to v1.7.x branch.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ccfbccb..3263ed3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -681,6 +681,7 @@
 endif()
 add_dependencies(buildtests_cxx channel_arguments_test)
 add_dependencies(buildtests_cxx channel_filter_test)
+add_dependencies(buildtests_cxx chttp2_settings_timeout_test)
 add_dependencies(buildtests_cxx cli_call_test)
 if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
 add_dependencies(buildtests_cxx client_crash_test)
@@ -9991,6 +9992,44 @@
 endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 
+add_executable(chttp2_settings_timeout_test
+  test/core/transport/chttp2/settings_timeout_test.cc
+  third_party/googletest/googletest/src/gtest-all.cc
+  third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+
+target_include_directories(chttp2_settings_timeout_test
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${BORINGSSL_ROOT_DIR}/include
+  PRIVATE ${PROTOBUF_ROOT_DIR}/src
+  PRIVATE ${BENCHMARK_ROOT_DIR}/include
+  PRIVATE ${ZLIB_ROOT_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib
+  PRIVATE ${CARES_INCLUDE_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/cares/cares
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/gflags/include
+  PRIVATE third_party/googletest/googletest/include
+  PRIVATE third_party/googletest/googletest
+  PRIVATE third_party/googletest/googlemock/include
+  PRIVATE third_party/googletest/googlemock
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(chttp2_settings_timeout_test
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_test_util
+  grpc
+  gpr_test_util
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+
 add_executable(cli_call_test
   test/cpp/util/cli_call_test.cc
   third_party/googletest/googletest/src/gtest-all.cc
diff --git a/Makefile b/Makefile
index 12d6d6e..c91aad3 100644
--- a/Makefile
+++ b/Makefile
@@ -1117,6 +1117,7 @@
 bm_pollset: $(BINDIR)/$(CONFIG)/bm_pollset
 channel_arguments_test: $(BINDIR)/$(CONFIG)/channel_arguments_test
 channel_filter_test: $(BINDIR)/$(CONFIG)/channel_filter_test
+chttp2_settings_timeout_test: $(BINDIR)/$(CONFIG)/chttp2_settings_timeout_test
 cli_call_test: $(BINDIR)/$(CONFIG)/cli_call_test
 client_crash_test: $(BINDIR)/$(CONFIG)/client_crash_test
 client_crash_test_server: $(BINDIR)/$(CONFIG)/client_crash_test_server
@@ -1562,6 +1563,7 @@
   $(BINDIR)/$(CONFIG)/bm_pollset \
   $(BINDIR)/$(CONFIG)/channel_arguments_test \
   $(BINDIR)/$(CONFIG)/channel_filter_test \
+  $(BINDIR)/$(CONFIG)/chttp2_settings_timeout_test \
   $(BINDIR)/$(CONFIG)/cli_call_test \
   $(BINDIR)/$(CONFIG)/client_crash_test \
   $(BINDIR)/$(CONFIG)/client_crash_test_server \
@@ -1682,6 +1684,7 @@
   $(BINDIR)/$(CONFIG)/bm_pollset \
   $(BINDIR)/$(CONFIG)/channel_arguments_test \
   $(BINDIR)/$(CONFIG)/channel_filter_test \
+  $(BINDIR)/$(CONFIG)/chttp2_settings_timeout_test \
   $(BINDIR)/$(CONFIG)/cli_call_test \
   $(BINDIR)/$(CONFIG)/client_crash_test \
   $(BINDIR)/$(CONFIG)/client_crash_test_server \
@@ -2071,6 +2074,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/channel_arguments_test || ( echo test channel_arguments_test failed ; exit 1 )
 	$(E) "[RUN]     Testing channel_filter_test"
 	$(Q) $(BINDIR)/$(CONFIG)/channel_filter_test || ( echo test channel_filter_test failed ; exit 1 )
+	$(E) "[RUN]     Testing chttp2_settings_timeout_test"
+	$(Q) $(BINDIR)/$(CONFIG)/chttp2_settings_timeout_test || ( echo test chttp2_settings_timeout_test failed ; exit 1 )
 	$(E) "[RUN]     Testing cli_call_test"
 	$(Q) $(BINDIR)/$(CONFIG)/cli_call_test || ( echo test cli_call_test failed ; exit 1 )
 	$(E) "[RUN]     Testing client_crash_test"
@@ -14494,6 +14499,49 @@
 endif
 
 
+CHTTP2_SETTINGS_TIMEOUT_TEST_SRC = \
+    test/core/transport/chttp2/settings_timeout_test.cc \
+
+CHTTP2_SETTINGS_TIMEOUT_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(CHTTP2_SETTINGS_TIMEOUT_TEST_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/chttp2_settings_timeout_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)/chttp2_settings_timeout_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/chttp2_settings_timeout_test: $(PROTOBUF_DEP) $(CHTTP2_SETTINGS_TIMEOUT_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) $(LDXX) $(LDFLAGS) $(CHTTP2_SETTINGS_TIMEOUT_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.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)/chttp2_settings_timeout_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/core/transport/chttp2/settings_timeout_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_chttp2_settings_timeout_test: $(CHTTP2_SETTINGS_TIMEOUT_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(CHTTP2_SETTINGS_TIMEOUT_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 CLI_CALL_TEST_SRC = \
     test/cpp/util/cli_call_test.cc \
 
diff --git a/build.yaml b/build.yaml
index ac5a6b2..309fa15 100644
--- a/build.yaml
+++ b/build.yaml
@@ -3800,6 +3800,17 @@
   - grpc++
   - grpc
   - gpr
+- name: chttp2_settings_timeout_test
+  gtest: true
+  build: test
+  language: c++
+  src:
+  - test/core/transport/chttp2/settings_timeout_test.cc
+  deps:
+  - grpc_test_util
+  - grpc
+  - gpr_test_util
+  - gpr
 - name: cli_call_test
   gtest: true
   build: test
diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h
index 65463bb..877bf92 100644
--- a/include/grpc/impl/codegen/grpc_types.h
+++ b/include/grpc/impl/codegen/grpc_types.h
@@ -240,6 +240,9 @@
 /** The time between the first and second connection attempts, in ms */
 #define GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS \
   "grpc.initial_reconnect_backoff_ms"
+/** The timeout used on servers for finishing handshaking on an incoming
+    connection.  Defaults to 120 seconds. */
+#define GRPC_ARG_SERVER_HANDSHAKE_TIMEOUT_MS "grpc.server_handshake_timeout_ms"
 /** This *should* be used for testing only.
     The caller of the secure_channel_create functions may override the target
     name used for SSL host name checking using this channel argument which is of
diff --git a/src/core/ext/transport/chttp2/client/chttp2_connector.c b/src/core/ext/transport/chttp2/client/chttp2_connector.c
index 202bcd4..a459a1a 100644
--- a/src/core/ext/transport/chttp2/client/chttp2_connector.c
+++ b/src/core/ext/transport/chttp2/client/chttp2_connector.c
@@ -115,11 +115,35 @@
     }
     memset(c->result, 0, sizeof(*c->result));
   } else {
-    c->result->transport =
-        grpc_create_chttp2_transport(exec_ctx, args->args, args->endpoint, 1);
+    c->result->transport = grpc_create_chttp2_transport(exec_ctx, args->args,
+                                                        args->endpoint, true);
     GPR_ASSERT(c->result->transport);
+    // TODO(roth): We ideally want to wait until we receive HTTP/2
+    // settings from the server before we consider the connection
+    // established.  If that doesn't happen before the connection
+    // timeout expires, then we should consider the connection attempt a
+    // failure and feed that information back into the backoff code.
+    // We could pass a notify_on_receive_settings callback to
+    // grpc_chttp2_transport_start_reading() to let us know when
+    // settings are received, but we would need to figure out how to use
+    // that information here.
+    //
+    // Unfortunately, we don't currently have a way to split apart the two
+    // effects of scheduling c->notify: we start sending RPCs immediately
+    // (which we want to do) and we consider the connection attempt successful
+    // (which we don't want to do until we get the notify_on_receive_settings
+    // callback from the transport).  If we could split those things
+    // apart, then we could start sending RPCs but then wait for our
+    // timeout before deciding if the connection attempt is successful.
+    // If the attempt is not successful, then we would tear down the
+    // transport and feed the failure back into the backoff code.
+    //
+    // In addition, even if we did that, we would probably not want to do
+    // so until after transparent retries is implemented.  Otherwise, any
+    // RPC that we attempt to send on the connection before the timeout
+    // would fail instead of being retried on a subsequent attempt.
     grpc_chttp2_transport_start_reading(exec_ctx, c->result->transport,
-                                        args->read_buffer);
+                                        args->read_buffer, NULL);
     c->result->channel_args = args->args;
   }
   grpc_closure *notify = c->notify;
diff --git a/src/core/ext/transport/chttp2/client/insecure/channel_create_posix.c b/src/core/ext/transport/chttp2/client/insecure/channel_create_posix.c
index dd88136..f150aa7 100644
--- a/src/core/ext/transport/chttp2/client/insecure/channel_create_posix.c
+++ b/src/core/ext/transport/chttp2/client/insecure/channel_create_posix.c
@@ -53,12 +53,12 @@
       &exec_ctx, grpc_fd_create(fd, "client"), args, "fd-client");
 
   grpc_transport *transport =
-      grpc_create_chttp2_transport(&exec_ctx, final_args, client, 1);
+      grpc_create_chttp2_transport(&exec_ctx, final_args, client, true);
   GPR_ASSERT(transport);
   grpc_channel *channel = grpc_channel_create(
       &exec_ctx, target, final_args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
   grpc_channel_args_destroy(&exec_ctx, final_args);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
 
   grpc_exec_ctx_finish(&exec_ctx);
 
diff --git a/src/core/ext/transport/chttp2/server/chttp2_server.c b/src/core/ext/transport/chttp2/server/chttp2_server.c
index 60244e1..5736078 100644
--- a/src/core/ext/transport/chttp2/server/chttp2_server.c
+++ b/src/core/ext/transport/chttp2/server/chttp2_server.c
@@ -20,6 +20,7 @@
 
 #include <grpc/grpc.h>
 
+#include <limits.h>
 #include <string.h>
 
 #include <grpc/support/alloc.h>
@@ -30,6 +31,7 @@
 
 #include "src/core/ext/filters/http/server/http_server_filter.h"
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
+#include "src/core/ext/transport/chttp2/transport/internal.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/handshaker.h"
 #include "src/core/lib/channel/handshaker_registry.h"
@@ -52,12 +54,52 @@
 } server_state;
 
 typedef struct {
+  gpr_refcount refs;
   server_state *svr_state;
   grpc_pollset *accepting_pollset;
   grpc_tcp_server_acceptor *acceptor;
   grpc_handshake_manager *handshake_mgr;
+  // State for enforcing handshake timeout on receiving HTTP/2 settings.
+  grpc_chttp2_transport *transport;
+  gpr_timespec deadline;
+  grpc_timer timer;
+  grpc_closure on_timeout;
+  grpc_closure on_receive_settings;
 } server_connection_state;
 
+static void server_connection_state_unref(
+    grpc_exec_ctx *exec_ctx, server_connection_state *connection_state) {
+  if (gpr_unref(&connection_state->refs)) {
+    if (connection_state->transport != NULL) {
+      GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, connection_state->transport,
+                                  "receive settings timeout");
+    }
+    gpr_free(connection_state);
+  }
+}
+
+static void on_timeout(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
+  server_connection_state *connection_state = (server_connection_state *)arg;
+  // Note that we may be called with GRPC_ERROR_NONE when the timer fires
+  // or with an error indicating that the timer system is being shut down.
+  if (error != GRPC_ERROR_CANCELLED) {
+    grpc_transport_op *op = grpc_make_transport_op(NULL);
+    op->disconnect_with_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "Did not receive HTTP/2 settings before handshake timeout");
+    grpc_transport_perform_op(exec_ctx, &connection_state->transport->base, op);
+  }
+  server_connection_state_unref(exec_ctx, connection_state);
+}
+
+static void on_receive_settings(grpc_exec_ctx *exec_ctx, void *arg,
+                                grpc_error *error) {
+  server_connection_state *connection_state = (server_connection_state *)arg;
+  if (error == GRPC_ERROR_NONE) {
+    grpc_timer_cancel(exec_ctx, &connection_state->timer);
+  }
+  server_connection_state_unref(exec_ctx, connection_state);
+}
+
 static void on_handshake_done(grpc_exec_ctx *exec_ctx, void *arg,
                               grpc_error *error) {
   grpc_handshaker_args *args = (grpc_handshaker_args *)arg;
@@ -67,7 +109,6 @@
   if (error != GRPC_ERROR_NONE || connection_state->svr_state->shutdown) {
     const char *error_str = grpc_error_string(error);
     gpr_log(GPR_DEBUG, "Handshaking failed: %s", error_str);
-
     if (error == GRPC_ERROR_NONE && args->endpoint != NULL) {
       // We were shut down after handshaking completed successfully, so
       // destroy the endpoint here.
@@ -86,14 +127,30 @@
     // handshaker may have handed off the connection to some external
     // code, so we can just clean up here without creating a transport.
     if (args->endpoint != NULL) {
-      grpc_transport *transport =
-          grpc_create_chttp2_transport(exec_ctx, args->args, args->endpoint, 0);
+      grpc_transport *transport = grpc_create_chttp2_transport(
+          exec_ctx, args->args, args->endpoint, false);
       grpc_server_setup_transport(
           exec_ctx, connection_state->svr_state->server, transport,
           connection_state->accepting_pollset, args->args);
-      grpc_chttp2_transport_start_reading(exec_ctx, transport,
-                                          args->read_buffer);
+      // Use notify_on_receive_settings callback to enforce the
+      // handshake deadline.
+      connection_state->transport = (grpc_chttp2_transport *)transport;
+      gpr_ref(&connection_state->refs);
+      GRPC_CLOSURE_INIT(&connection_state->on_receive_settings,
+                        on_receive_settings, connection_state,
+                        grpc_schedule_on_exec_ctx);
+      grpc_chttp2_transport_start_reading(
+          exec_ctx, transport, args->read_buffer,
+          &connection_state->on_receive_settings);
       grpc_channel_args_destroy(exec_ctx, args->args);
+      gpr_ref(&connection_state->refs);
+      GRPC_CHTTP2_REF_TRANSPORT((grpc_chttp2_transport *)transport,
+                                "receive settings timeout");
+      GRPC_CLOSURE_INIT(&connection_state->on_timeout, on_timeout,
+                        connection_state, grpc_schedule_on_exec_ctx);
+      grpc_timer_init(exec_ctx, &connection_state->timer,
+                      connection_state->deadline, &connection_state->on_timeout,
+                      gpr_now(GPR_CLOCK_MONOTONIC));
     }
   }
   grpc_handshake_manager_pending_list_remove(
@@ -101,9 +158,9 @@
       connection_state->handshake_mgr);
   gpr_mu_unlock(&connection_state->svr_state->mu);
   grpc_handshake_manager_destroy(exec_ctx, connection_state->handshake_mgr);
-  grpc_tcp_server_unref(exec_ctx, connection_state->svr_state->tcp_server);
   gpr_free(connection_state->acceptor);
-  gpr_free(connection_state);
+  grpc_tcp_server_unref(exec_ctx, connection_state->svr_state->tcp_server);
+  server_connection_state_unref(exec_ctx, connection_state);
 }
 
 static void on_accept(grpc_exec_ctx *exec_ctx, void *arg, grpc_endpoint *tcp,
@@ -124,19 +181,26 @@
   gpr_mu_unlock(&state->mu);
   grpc_tcp_server_ref(state->tcp_server);
   server_connection_state *connection_state =
-      (server_connection_state *)gpr_malloc(sizeof(*connection_state));
+      (server_connection_state *)gpr_zalloc(sizeof(*connection_state));
+  gpr_ref_init(&connection_state->refs, 1);
   connection_state->svr_state = state;
   connection_state->accepting_pollset = accepting_pollset;
   connection_state->acceptor = acceptor;
   connection_state->handshake_mgr = handshake_mgr;
   grpc_handshakers_add(exec_ctx, HANDSHAKER_SERVER, state->args,
                        connection_state->handshake_mgr);
-  // TODO(roth): We should really get this timeout value from channel
-  // args instead of hard-coding it.
-  const gpr_timespec deadline = gpr_time_add(
-      gpr_now(GPR_CLOCK_MONOTONIC), gpr_time_from_seconds(120, GPR_TIMESPAN));
+  const grpc_arg *timeout_arg =
+      grpc_channel_args_find(state->args, GRPC_ARG_SERVER_HANDSHAKE_TIMEOUT_MS);
+  connection_state->deadline = gpr_time_add(
+      gpr_now(GPR_CLOCK_MONOTONIC),
+      gpr_time_from_millis(
+          grpc_channel_arg_get_integer(
+              timeout_arg,
+              (grpc_integer_options){120 * GPR_MS_PER_SEC, 1, INT_MAX}),
+          GPR_TIMESPAN));
   grpc_handshake_manager_do_handshake(exec_ctx, connection_state->handshake_mgr,
-                                      tcp, state->args, deadline, acceptor,
+                                      tcp, state->args,
+                                      connection_state->deadline, acceptor,
                                       on_handshake_done, connection_state);
 }
 
diff --git a/src/core/ext/transport/chttp2/server/insecure/server_chttp2_posix.c b/src/core/ext/transport/chttp2/server/insecure/server_chttp2_posix.c
index e647067..98b15bc 100644
--- a/src/core/ext/transport/chttp2/server/insecure/server_chttp2_posix.c
+++ b/src/core/ext/transport/chttp2/server/insecure/server_chttp2_posix.c
@@ -50,7 +50,7 @@
 
   const grpc_channel_args *server_args = grpc_server_get_channel_args(server);
   grpc_transport *transport = grpc_create_chttp2_transport(
-      &exec_ctx, server_args, server_endpoint, 0 /* is_client */);
+      &exec_ctx, server_args, server_endpoint, false /* is_client */);
 
   grpc_pollset **pollsets;
   size_t num_pollsets = 0;
@@ -61,7 +61,7 @@
   }
 
   grpc_server_setup_transport(&exec_ctx, server, transport, NULL, server_args);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.c b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
index acf4963..2ba6717 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.c
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.c
@@ -663,6 +663,11 @@
     end_all_the_calls(exec_ctx, t, GRPC_ERROR_REF(error));
     cancel_pings(exec_ctx, t, GRPC_ERROR_REF(error));
   }
+  if (t->notify_on_receive_settings != NULL) {
+    GRPC_CLOSURE_SCHED(exec_ctx, t->notify_on_receive_settings,
+                       GRPC_ERROR_CANCELLED);
+    t->notify_on_receive_settings = NULL;
+  }
   GRPC_ERROR_UNREF(error);
 }
 
@@ -1802,7 +1807,6 @@
   grpc_transport_op *op = (grpc_transport_op *)stream_op;
   grpc_chttp2_transport *t =
       (grpc_chttp2_transport *)op->handler_private.extra_arg;
-  grpc_error *close_transport = op->disconnect_with_error;
 
   if (op->goaway_error) {
     send_goaway(exec_ctx, t, op->goaway_error);
@@ -1834,8 +1838,8 @@
         op->on_connectivity_state_change);
   }
 
-  if (close_transport != GRPC_ERROR_NONE) {
-    close_transport_locked(exec_ctx, t, close_transport);
+  if (op->disconnect_with_error != GRPC_ERROR_NONE) {
+    close_transport_locked(exec_ctx, t, op->disconnect_with_error);
   }
 
   GRPC_CLOSURE_RUN(exec_ctx, op->on_consumed, GRPC_ERROR_NONE);
@@ -3231,16 +3235,16 @@
 
 grpc_transport *grpc_create_chttp2_transport(
     grpc_exec_ctx *exec_ctx, const grpc_channel_args *channel_args,
-    grpc_endpoint *ep, int is_client) {
+    grpc_endpoint *ep, bool is_client) {
   grpc_chttp2_transport *t =
       (grpc_chttp2_transport *)gpr_zalloc(sizeof(grpc_chttp2_transport));
-  init_transport(exec_ctx, t, channel_args, ep, is_client != 0);
+  init_transport(exec_ctx, t, channel_args, ep, is_client);
   return &t->base;
 }
 
-void grpc_chttp2_transport_start_reading(grpc_exec_ctx *exec_ctx,
-                                         grpc_transport *transport,
-                                         grpc_slice_buffer *read_buffer) {
+void grpc_chttp2_transport_start_reading(
+    grpc_exec_ctx *exec_ctx, grpc_transport *transport,
+    grpc_slice_buffer *read_buffer, grpc_closure *notify_on_receive_settings) {
   grpc_chttp2_transport *t = (grpc_chttp2_transport *)transport;
   GRPC_CHTTP2_REF_TRANSPORT(
       t, "reading_action"); /* matches unref inside reading_action */
@@ -3248,5 +3252,6 @@
     grpc_slice_buffer_move_into(read_buffer, &t->read_buffer);
     gpr_free(read_buffer);
   }
+  t->notify_on_receive_settings = notify_on_receive_settings;
   GRPC_CLOSURE_SCHED(exec_ctx, &t->read_action_locked, GRPC_ERROR_NONE);
 }
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.h b/src/core/ext/transport/chttp2/transport/chttp2_transport.h
index 55fb1a8..1f9d384 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.h
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.h
@@ -33,12 +33,14 @@
 
 grpc_transport *grpc_create_chttp2_transport(
     grpc_exec_ctx *exec_ctx, const grpc_channel_args *channel_args,
-    grpc_endpoint *ep, int is_client);
+    grpc_endpoint *ep, bool is_client);
 
 /// Takes ownership of \a read_buffer, which (if non-NULL) contains
 /// leftover bytes previously read from the endpoint (e.g., by handshakers).
-void grpc_chttp2_transport_start_reading(grpc_exec_ctx *exec_ctx,
-                                         grpc_transport *transport,
-                                         grpc_slice_buffer *read_buffer);
+/// If non-null, \a notify_on_receive_settings will be scheduled when
+/// HTTP/2 settings are received from the peer.
+void grpc_chttp2_transport_start_reading(
+    grpc_exec_ctx *exec_ctx, grpc_transport *transport,
+    grpc_slice_buffer *read_buffer, grpc_closure *notify_on_receive_settings);
 
 #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_CHTTP2_TRANSPORT_H */
diff --git a/src/core/ext/transport/chttp2/transport/frame_settings.c b/src/core/ext/transport/chttp2/transport/frame_settings.c
index 2995bf7..637f305 100644
--- a/src/core/ext/transport/chttp2/transport/frame_settings.c
+++ b/src/core/ext/transport/chttp2/transport/frame_settings.c
@@ -131,6 +131,11 @@
             memcpy(parser->target_settings, parser->incoming_settings,
                    GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t));
             grpc_slice_buffer_add(&t->qbuf, grpc_chttp2_settings_ack_create());
+            if (t->notify_on_receive_settings != NULL) {
+              GRPC_CLOSURE_SCHED(exec_ctx, t->notify_on_receive_settings,
+                                 GRPC_ERROR_NONE);
+              t->notify_on_receive_settings = NULL;
+            }
           }
           return GRPC_ERROR_NONE;
         }
diff --git a/src/core/ext/transport/chttp2/transport/internal.h b/src/core/ext/transport/chttp2/transport/internal.h
index 4902215..fde46b3 100644
--- a/src/core/ext/transport/chttp2/transport/internal.h
+++ b/src/core/ext/transport/chttp2/transport/internal.h
@@ -287,6 +287,8 @@
 
   grpc_combiner *combiner;
 
+  grpc_closure *notify_on_receive_settings;
+
   /** write execution state of the transport */
   grpc_chttp2_write_state write_state;
   /** is this the first write in a series of writes?
diff --git a/src/core/lib/transport/transport.h b/src/core/lib/transport/transport.h
index fbf5dcb..2cc3e37 100644
--- a/src/core/lib/transport/transport.h
+++ b/src/core/lib/transport/transport.h
@@ -333,9 +333,6 @@
 void grpc_transport_goaway(grpc_transport *transport, grpc_status_code status,
                            grpc_slice debug_data);
 
-/* Close a transport. Aborts all open streams. */
-void grpc_transport_close(grpc_transport *transport);
-
 /* Destroy the transport */
 void grpc_transport_destroy(grpc_exec_ctx *exec_ctx, grpc_transport *transport);
 
diff --git a/test/core/bad_client/bad_client.c b/test/core/bad_client/bad_client.c
index fff0c79..93b3053 100644
--- a/test/core/bad_client/bad_client.c
+++ b/test/core/bad_client/bad_client.c
@@ -110,9 +110,9 @@
                                   GRPC_BAD_CLIENT_REGISTERED_HOST,
                                   GRPC_SRM_PAYLOAD_READ_INITIAL_BYTE_BUFFER, 0);
   grpc_server_start(a.server);
-  transport = grpc_create_chttp2_transport(&exec_ctx, NULL, sfd.server, 0);
+  transport = grpc_create_chttp2_transport(&exec_ctx, NULL, sfd.server, false);
   server_setup_transport(&a, transport);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 
   /* Bind everything into the same pollset */
diff --git a/test/core/end2end/fixtures/h2_sockpair+trace.c b/test/core/end2end/fixtures/h2_sockpair+trace.c
index 39ccb84..da3954d 100644
--- a/test/core/end2end/fixtures/h2_sockpair+trace.c
+++ b/test/core/end2end/fixtures/h2_sockpair+trace.c
@@ -96,10 +96,10 @@
   cs.client_args = client_args;
   cs.f = f;
   transport =
-      grpc_create_chttp2_transport(&exec_ctx, client_args, sfd->client, 1);
+      grpc_create_chttp2_transport(&exec_ctx, client_args, sfd->client, true);
   client_setup_transport(&exec_ctx, &cs, transport);
   GPR_ASSERT(f->client);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
@@ -113,9 +113,9 @@
   grpc_server_register_completion_queue(f->server, f->cq, NULL);
   grpc_server_start(f->server);
   transport =
-      grpc_create_chttp2_transport(&exec_ctx, server_args, sfd->server, 0);
+      grpc_create_chttp2_transport(&exec_ctx, server_args, sfd->server, false);
   server_setup_transport(f, transport);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
diff --git a/test/core/end2end/fixtures/h2_sockpair.c b/test/core/end2end/fixtures/h2_sockpair.c
index 03566ea..cbc8d4b 100644
--- a/test/core/end2end/fixtures/h2_sockpair.c
+++ b/test/core/end2end/fixtures/h2_sockpair.c
@@ -90,10 +90,10 @@
   cs.client_args = client_args;
   cs.f = f;
   transport =
-      grpc_create_chttp2_transport(&exec_ctx, client_args, sfd->client, 1);
+      grpc_create_chttp2_transport(&exec_ctx, client_args, sfd->client, true);
   client_setup_transport(&exec_ctx, &cs, transport);
   GPR_ASSERT(f->client);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
@@ -107,9 +107,9 @@
   grpc_server_register_completion_queue(f->server, f->cq, NULL);
   grpc_server_start(f->server);
   transport =
-      grpc_create_chttp2_transport(&exec_ctx, server_args, sfd->server, 0);
+      grpc_create_chttp2_transport(&exec_ctx, server_args, sfd->server, false);
   server_setup_transport(f, transport);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
diff --git a/test/core/end2end/fixtures/h2_sockpair_1byte.c b/test/core/end2end/fixtures/h2_sockpair_1byte.c
index c75a387..6030fd6 100644
--- a/test/core/end2end/fixtures/h2_sockpair_1byte.c
+++ b/test/core/end2end/fixtures/h2_sockpair_1byte.c
@@ -100,10 +100,10 @@
   cs.client_args = client_args;
   cs.f = f;
   transport =
-      grpc_create_chttp2_transport(&exec_ctx, client_args, sfd->client, 1);
+      grpc_create_chttp2_transport(&exec_ctx, client_args, sfd->client, true);
   client_setup_transport(&exec_ctx, &cs, transport);
   GPR_ASSERT(f->client);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
@@ -117,9 +117,9 @@
   grpc_server_register_completion_queue(f->server, f->cq, NULL);
   grpc_server_start(f->server);
   transport =
-      grpc_create_chttp2_transport(&exec_ctx, server_args, sfd->server, 0);
+      grpc_create_chttp2_transport(&exec_ctx, server_args, sfd->server, false);
   server_setup_transport(f, transport);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
diff --git a/test/core/end2end/fuzzers/api_fuzzer.c b/test/core/end2end/fuzzers/api_fuzzer.c
index 1228c9f..ab8085a 100644
--- a/test/core/end2end/fuzzers/api_fuzzer.c
+++ b/test/core/end2end/fuzzers/api_fuzzer.c
@@ -462,9 +462,9 @@
     *fc->ep = client;
 
     grpc_transport *transport =
-        grpc_create_chttp2_transport(exec_ctx, NULL, server, 0);
+        grpc_create_chttp2_transport(exec_ctx, NULL, server, false);
     grpc_server_setup_transport(exec_ctx, g_server, transport, NULL, NULL);
-    grpc_chttp2_transport_start_reading(exec_ctx, transport, NULL);
+    grpc_chttp2_transport_start_reading(exec_ctx, transport, NULL, NULL);
 
     GRPC_CLOSURE_SCHED(exec_ctx, fc->closure, GRPC_ERROR_NONE);
   } else {
diff --git a/test/core/end2end/fuzzers/client_fuzzer.c b/test/core/end2end/fuzzers/client_fuzzer.c
index 88ba6ba..98bda85 100644
--- a/test/core/end2end/fuzzers/client_fuzzer.c
+++ b/test/core/end2end/fuzzers/client_fuzzer.c
@@ -54,8 +54,8 @@
 
   grpc_completion_queue *cq = grpc_completion_queue_create_for_next(NULL);
   grpc_transport *transport =
-      grpc_create_chttp2_transport(&exec_ctx, NULL, mock_endpoint, 1);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+      grpc_create_chttp2_transport(&exec_ctx, NULL, mock_endpoint, true);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
 
   grpc_channel *channel = grpc_channel_create(
       &exec_ctx, "test-target", NULL, GRPC_CLIENT_DIRECT_CHANNEL, transport);
diff --git a/test/core/end2end/fuzzers/server_fuzzer.c b/test/core/end2end/fuzzers/server_fuzzer.c
index ef4c0a4..52635b8 100644
--- a/test/core/end2end/fuzzers/server_fuzzer.c
+++ b/test/core/end2end/fuzzers/server_fuzzer.c
@@ -61,9 +61,9 @@
   //    grpc_server_register_method(server, "/reg", NULL, 0);
   grpc_server_start(server);
   grpc_transport *transport =
-      grpc_create_chttp2_transport(&exec_ctx, NULL, mock_endpoint, 0);
+      grpc_create_chttp2_transport(&exec_ctx, NULL, mock_endpoint, false);
   grpc_server_setup_transport(&exec_ctx, server, transport, NULL, NULL);
-  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+  grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
 
   grpc_call *call1 = NULL;
   grpc_call_details call_details1;
diff --git a/test/core/transport/chttp2/BUILD b/test/core/transport/chttp2/BUILD
index 6081940..2a78ff0 100644
--- a/test/core/transport/chttp2/BUILD
+++ b/test/core/transport/chttp2/BUILD
@@ -115,6 +115,21 @@
 )
 
 grpc_cc_test(
+    name = "settings_timeout_test",
+    srcs = ["settings_timeout_test.cc"],
+    language = "C++",
+    deps = [
+        "//:gpr",
+        "//:grpc",
+        "//test/core/util:gpr_test_util",
+        "//test/core/util:grpc_test_util",
+    ],
+    external_deps = [
+        "gtest",
+    ],
+)
+
+grpc_cc_test(
     name = "varint_test",
     srcs = ["varint_test.c"],
     language = "C",
diff --git a/test/core/transport/chttp2/settings_timeout_test.cc b/test/core/transport/chttp2/settings_timeout_test.cc
new file mode 100644
index 0000000..7bf6cfe
--- /dev/null
+++ b/test/core/transport/chttp2/settings_timeout_test.cc
@@ -0,0 +1,267 @@
+/*
+ *
+ * Copyright 2017 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+
+#include <memory>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "src/core/lib/iomgr/endpoint.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/pollset.h"
+#include "src/core/lib/iomgr/pollset_set.h"
+#include "src/core/lib/iomgr/resolve_address.h"
+#include "src/core/lib/iomgr/tcp_client.h"
+#include "src/core/lib/slice/slice_internal.h"
+}
+
+#include "test/core/util/port.h"
+#include "test/core/util/test_config.h"
+
+namespace grpc_core {
+namespace test {
+namespace {
+
+// A gRPC server, running in its own thread.
+class ServerThread {
+ public:
+  explicit ServerThread(const char* address) : address_(address) {}
+
+  void Start() {
+    // Start server with 1-second handshake timeout.
+    grpc_arg arg;
+    arg.type = GRPC_ARG_INTEGER;
+    arg.key = const_cast<char*>(GRPC_ARG_SERVER_HANDSHAKE_TIMEOUT_MS);
+    arg.value.integer = 1000;
+    grpc_channel_args args = {1, &arg};
+    server_ = grpc_server_create(&args, nullptr);
+    ASSERT_TRUE(grpc_server_add_insecure_http2_port(server_, address_));
+    cq_ = grpc_completion_queue_create_for_next(nullptr);
+    grpc_server_register_completion_queue(server_, cq_, nullptr);
+    grpc_server_start(server_);
+    thread_.reset(new std::thread(std::bind(&ServerThread::Serve, this)));
+  }
+
+  void Shutdown() {
+    grpc_completion_queue* shutdown_cq =
+        grpc_completion_queue_create_for_pluck(nullptr);
+    grpc_server_shutdown_and_notify(server_, shutdown_cq, nullptr);
+    GPR_ASSERT(grpc_completion_queue_pluck(shutdown_cq, nullptr,
+                                           grpc_timeout_seconds_to_deadline(1),
+                                           nullptr)
+                   .type == GRPC_OP_COMPLETE);
+    grpc_completion_queue_destroy(shutdown_cq);
+    grpc_server_destroy(server_);
+    grpc_completion_queue_destroy(cq_);
+    thread_->join();
+  }
+
+ private:
+  void Serve() {
+    // The completion queue should not return anything other than shutdown.
+    grpc_event ev = grpc_completion_queue_next(
+        cq_, gpr_inf_future(GPR_CLOCK_MONOTONIC), nullptr);
+    ASSERT_EQ(GRPC_QUEUE_SHUTDOWN, ev.type);
+  }
+
+  const char* address_;  // Do not own.
+  grpc_server* server_ = nullptr;
+  grpc_completion_queue* cq_ = nullptr;
+  std::unique_ptr<std::thread> thread_;
+};
+
+// A TCP client that connects to the server, reads data until the server
+// closes, and then terminates.
+class Client {
+ public:
+  explicit Client(const char* server_address)
+      : server_address_(server_address) {}
+
+  void Connect() {
+    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+    grpc_resolved_addresses* server_addresses = nullptr;
+    grpc_error* error =
+        grpc_blocking_resolve_address(server_address_, "80", &server_addresses);
+    ASSERT_EQ(GRPC_ERROR_NONE, error) << grpc_error_string(error);
+    ASSERT_GE(server_addresses->naddrs, 1UL);
+    pollset_ = (grpc_pollset*)gpr_zalloc(grpc_pollset_size());
+    grpc_pollset_init(pollset_, &mu_);
+    grpc_pollset_set* pollset_set = grpc_pollset_set_create();
+    grpc_pollset_set_add_pollset(&exec_ctx, pollset_set, pollset_);
+    EventState state;
+    grpc_tcp_client_connect(
+        &exec_ctx, state.closure(), &endpoint_, pollset_set,
+        nullptr /* channel_args */, server_addresses->addrs,
+        gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+                     gpr_time_from_seconds(1, GPR_TIMESPAN)));
+    ASSERT_TRUE(
+        PollUntilDone(&exec_ctx, &state, gpr_inf_future(GPR_CLOCK_MONOTONIC)));
+    ASSERT_EQ(GRPC_ERROR_NONE, state.error());
+    grpc_pollset_set_destroy(&exec_ctx, pollset_set);
+    grpc_endpoint_add_to_pollset(&exec_ctx, endpoint_, pollset_);
+    grpc_resolved_addresses_destroy(server_addresses);
+    grpc_exec_ctx_finish(&exec_ctx);
+  }
+
+  // Reads until an error is returned.
+  // Returns true if an error was encountered before the deadline.
+  bool ReadUntilError() {
+    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+    grpc_slice_buffer read_buffer;
+    grpc_slice_buffer_init(&read_buffer);
+    bool retval = true;
+    // Use a deadline of 3 seconds, which is a lot more than we should
+    // need for a 1-second timeout, but this helps avoid flakes.
+    gpr_timespec deadline = gpr_time_add(
+        gpr_now(GPR_CLOCK_MONOTONIC), gpr_time_from_seconds(3, GPR_TIMESPAN));
+    while (true) {
+      EventState state;
+      grpc_endpoint_read(&exec_ctx, endpoint_, &read_buffer, state.closure());
+      if (!PollUntilDone(&exec_ctx, &state, deadline)) {
+        retval = false;
+        break;
+      }
+      if (state.error() != GRPC_ERROR_NONE) break;
+      gpr_log(GPR_INFO, "client read %" PRIuPTR " bytes", read_buffer.length);
+      grpc_slice_buffer_reset_and_unref_internal(&exec_ctx, &read_buffer);
+    }
+    grpc_endpoint_shutdown(&exec_ctx, endpoint_,
+                           GRPC_ERROR_CREATE_FROM_STATIC_STRING("shutdown"));
+    grpc_slice_buffer_destroy_internal(&exec_ctx, &read_buffer);
+    grpc_exec_ctx_finish(&exec_ctx);
+    return retval;
+  }
+
+  void Shutdown() {
+    grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+    grpc_endpoint_destroy(&exec_ctx, endpoint_);
+    grpc_pollset_shutdown(&exec_ctx, pollset_,
+                          GRPC_CLOSURE_CREATE(&Client::PollsetDestroy, pollset_,
+                                              grpc_schedule_on_exec_ctx));
+    grpc_exec_ctx_finish(&exec_ctx);
+  }
+
+ private:
+  // State used to wait for an I/O event.
+  class EventState {
+   public:
+    EventState() {
+      GRPC_CLOSURE_INIT(&closure_, &EventState::OnEventDone, this,
+                        grpc_schedule_on_exec_ctx);
+    }
+
+    ~EventState() { GRPC_ERROR_UNREF(error_); }
+
+    grpc_closure* closure() { return &closure_; }
+
+    bool done() const { return done_; }
+
+    // Caller does NOT take ownership of the error.
+    grpc_error* error() const { return error_; }
+
+   private:
+    static void OnEventDone(grpc_exec_ctx* exec_ctx, void* arg,
+                            grpc_error* error) {
+      gpr_log(GPR_INFO, "OnEventDone(): %s", grpc_error_string(error));
+      EventState* state = (EventState*)arg;
+      state->error_ = GRPC_ERROR_REF(error);
+      state->done_ = true;
+    }
+
+    grpc_closure closure_;
+    bool done_ = false;
+    grpc_error* error_ = GRPC_ERROR_NONE;
+  };
+
+  // Returns true if done, or false if deadline exceeded.
+  bool PollUntilDone(grpc_exec_ctx* exec_ctx, EventState* state,
+                     gpr_timespec deadline) {
+    while (true) {
+      grpc_pollset_worker* worker = nullptr;
+      gpr_mu_lock(mu_);
+      gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC);
+      GRPC_LOG_IF_ERROR(
+          "grpc_pollset_work",
+          grpc_pollset_work(
+              exec_ctx, pollset_, &worker, now,
+              gpr_time_add(now, gpr_time_from_seconds(1, GPR_TIMESPAN))));
+      gpr_mu_unlock(mu_);
+      if (state != nullptr && state->done()) return true;
+      if (gpr_time_cmp(gpr_now(GPR_CLOCK_MONOTONIC), deadline) > 0) {
+        return false;
+      }
+    }
+  }
+
+  static void PollsetDestroy(grpc_exec_ctx* exec_ctx, void* arg,
+                             grpc_error* error) {
+    grpc_pollset* pollset = (grpc_pollset*)arg;
+    grpc_pollset_destroy(exec_ctx, pollset);
+    gpr_free(pollset);
+  }
+
+  const char* server_address_;  // Do not own.
+  grpc_endpoint* endpoint_;
+  gpr_mu* mu_;
+  grpc_pollset* pollset_;
+};
+
+TEST(SettingsTimeout, Basic) {
+  // Construct server address string.
+  const int server_port = grpc_pick_unused_port_or_die();
+  char* server_address_string;
+  gpr_asprintf(&server_address_string, "localhost:%d", server_port);
+  // Start server.
+  gpr_log(GPR_INFO, "starting server on %s", server_address_string);
+  ServerThread server_thread(server_address_string);
+  server_thread.Start();
+  // Create client and connect to server.
+  gpr_log(GPR_INFO, "starting client connect");
+  Client client(server_address_string);
+  client.Connect();
+  // Client read.  Should fail due to server dropping connection.
+  gpr_log(GPR_INFO, "starting client read");
+  EXPECT_TRUE(client.ReadUntilError());
+  // Shut down client.
+  gpr_log(GPR_INFO, "shutting down client");
+  client.Shutdown();
+  // Shut down server.
+  gpr_log(GPR_INFO, "shutting down server");
+  server_thread.Shutdown();
+  // Clean up.
+  gpr_free(server_address_string);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace grpc_core
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  grpc_test_init(argc, argv);
+  grpc_init();
+  int result = RUN_ALL_TESTS();
+  grpc_shutdown();
+  return result;
+}
diff --git a/test/cpp/microbenchmarks/bm_chttp2_transport.cc b/test/cpp/microbenchmarks/bm_chttp2_transport.cc
index 070034f..82ed9f9 100644
--- a/test/cpp/microbenchmarks/bm_chttp2_transport.cc
+++ b/test/cpp/microbenchmarks/bm_chttp2_transport.cc
@@ -130,7 +130,7 @@
     grpc_channel_args c_args = args.c_channel_args();
     ep_ = new DummyEndpoint;
     t_ = grpc_create_chttp2_transport(exec_ctx(), &c_args, ep_, client);
-    grpc_chttp2_transport_start_reading(exec_ctx(), t_, NULL);
+    grpc_chttp2_transport_start_reading(exec_ctx(), t_, NULL, NULL);
     FlushExecCtx();
   }
 
diff --git a/test/cpp/microbenchmarks/fullstack_fixtures.h b/test/cpp/microbenchmarks/fullstack_fixtures.h
index ecd28c3..958afcf 100644
--- a/test/cpp/microbenchmarks/fullstack_fixtures.h
+++ b/test/cpp/microbenchmarks/fullstack_fixtures.h
@@ -175,7 +175,7 @@
       const grpc_channel_args* server_args =
           grpc_server_get_channel_args(server_->c_server());
       server_transport_ = grpc_create_chttp2_transport(
-          &exec_ctx, server_args, endpoints.server, 0 /* is_client */);
+          &exec_ctx, server_args, endpoints.server, false /* is_client */);
 
       grpc_pollset** pollsets;
       size_t num_pollsets = 0;
@@ -187,7 +187,8 @@
 
       grpc_server_setup_transport(&exec_ctx, server_->c_server(),
                                   server_transport_, NULL, server_args);
-      grpc_chttp2_transport_start_reading(&exec_ctx, server_transport_, NULL);
+      grpc_chttp2_transport_start_reading(&exec_ctx, server_transport_, NULL,
+                                          NULL);
     }
 
     /* create channel */
@@ -197,13 +198,14 @@
       fixture_configuration.ApplyCommonChannelArguments(&args);
 
       grpc_channel_args c_args = args.c_channel_args();
-      client_transport_ =
-          grpc_create_chttp2_transport(&exec_ctx, &c_args, endpoints.client, 1);
+      client_transport_ = grpc_create_chttp2_transport(&exec_ctx, &c_args,
+                                                       endpoints.client, true);
       GPR_ASSERT(client_transport_);
       grpc_channel* channel =
           grpc_channel_create(&exec_ctx, "target", &c_args,
                               GRPC_CLIENT_DIRECT_CHANNEL, client_transport_);
-      grpc_chttp2_transport_start_reading(&exec_ctx, client_transport_, NULL);
+      grpc_chttp2_transport_start_reading(&exec_ctx, client_transport_, NULL,
+                                          NULL);
 
       channel_ = CreateChannelInternal("", channel);
     }
diff --git a/test/cpp/performance/writes_per_rpc_test.cc b/test/cpp/performance/writes_per_rpc_test.cc
index f4f794c..6644e26 100644
--- a/test/cpp/performance/writes_per_rpc_test.cc
+++ b/test/cpp/performance/writes_per_rpc_test.cc
@@ -90,7 +90,7 @@
       const grpc_channel_args* server_args =
           grpc_server_get_channel_args(server_->c_server());
       grpc_transport* transport = grpc_create_chttp2_transport(
-          &exec_ctx, server_args, endpoints.server, 0 /* is_client */);
+          &exec_ctx, server_args, endpoints.server, false /* is_client */);
 
       grpc_pollset** pollsets;
       size_t num_pollsets = 0;
@@ -102,7 +102,7 @@
 
       grpc_server_setup_transport(&exec_ctx, server_->c_server(), transport,
                                   NULL, server_args);
-      grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+      grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
     }
 
     /* create channel */
@@ -112,12 +112,12 @@
       ApplyCommonChannelArguments(&args);
 
       grpc_channel_args c_args = args.c_channel_args();
-      grpc_transport* transport =
-          grpc_create_chttp2_transport(&exec_ctx, &c_args, endpoints.client, 1);
+      grpc_transport* transport = grpc_create_chttp2_transport(
+          &exec_ctx, &c_args, endpoints.client, true);
       GPR_ASSERT(transport);
       grpc_channel* channel = grpc_channel_create(
           &exec_ctx, "target", &c_args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
-      grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL);
+      grpc_chttp2_transport_start_reading(&exec_ctx, transport, NULL, NULL);
 
       channel_ = CreateChannelInternal("", channel);
     }
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index eaf4eda..a6cb51f 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -2952,6 +2952,23 @@
       "gpr", 
       "gpr_test_util", 
       "grpc", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "chttp2_settings_timeout_test", 
+    "src": [
+      "test/core/transport/chttp2/settings_timeout_test.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
+  {
+    "deps": [
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
       "grpc++", 
       "grpc++_test_util", 
       "grpc_cli_libs", 
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index 72dfbc0..8f08b1f 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -3211,6 +3211,28 @@
     "flaky": false, 
     "gtest": true, 
     "language": "c++", 
+    "name": "chttp2_settings_timeout_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ]
+  }, 
+  {
+    "args": [], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": true, 
+    "language": "c++", 
     "name": "cli_call_test", 
     "platforms": [
       "linux",