Merge pull request #4484 from alfatraining/fix_test/gpr_stack_lockfree_test

fix gpr_stack_lockfree_test assert bug on 64 bits
diff --git a/Makefile b/Makefile
index 02f1637..58a5011 100644
--- a/Makefile
+++ b/Makefile
@@ -1960,6 +1960,7 @@
 connection_prefix_bad_client_test: $(BINDIR)/$(CONFIG)/connection_prefix_bad_client_test
 headers_bad_client_test: $(BINDIR)/$(CONFIG)/headers_bad_client_test
 initial_settings_frame_bad_client_test: $(BINDIR)/$(CONFIG)/initial_settings_frame_bad_client_test
+server_registered_method_bad_client_test: $(BINDIR)/$(CONFIG)/server_registered_method_bad_client_test
 simple_request_bad_client_test: $(BINDIR)/$(CONFIG)/simple_request_bad_client_test
 unknown_frame_bad_client_test: $(BINDIR)/$(CONFIG)/unknown_frame_bad_client_test
 bad_ssl_alpn_server: $(BINDIR)/$(CONFIG)/bad_ssl_alpn_server
@@ -3222,6 +3223,7 @@
   $(BINDIR)/$(CONFIG)/connection_prefix_bad_client_test \
   $(BINDIR)/$(CONFIG)/headers_bad_client_test \
   $(BINDIR)/$(CONFIG)/initial_settings_frame_bad_client_test \
+  $(BINDIR)/$(CONFIG)/server_registered_method_bad_client_test \
   $(BINDIR)/$(CONFIG)/simple_request_bad_client_test \
   $(BINDIR)/$(CONFIG)/unknown_frame_bad_client_test \
   $(BINDIR)/$(CONFIG)/bad_ssl_alpn_server \
@@ -5540,6 +5542,8 @@
 	$(Q) $(BINDIR)/$(CONFIG)/headers_bad_client_test || ( echo test headers_bad_client_test failed ; exit 1 )
 	$(E) "[RUN]     Testing initial_settings_frame_bad_client_test"
 	$(Q) $(BINDIR)/$(CONFIG)/initial_settings_frame_bad_client_test || ( echo test initial_settings_frame_bad_client_test failed ; exit 1 )
+	$(E) "[RUN]     Testing server_registered_method_bad_client_test"
+	$(Q) $(BINDIR)/$(CONFIG)/server_registered_method_bad_client_test || ( echo test server_registered_method_bad_client_test failed ; exit 1 )
 	$(E) "[RUN]     Testing simple_request_bad_client_test"
 	$(Q) $(BINDIR)/$(CONFIG)/simple_request_bad_client_test || ( echo test simple_request_bad_client_test failed ; exit 1 )
 	$(E) "[RUN]     Testing unknown_frame_bad_client_test"
@@ -29229,6 +29233,23 @@
 endif
 
 
+SERVER_REGISTERED_METHOD_BAD_CLIENT_TEST_SRC = \
+    test/core/bad_client/tests/server_registered_method.c \
+
+SERVER_REGISTERED_METHOD_BAD_CLIENT_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(SERVER_REGISTERED_METHOD_BAD_CLIENT_TEST_SRC))))
+$(BINDIR)/$(CONFIG)/server_registered_method_bad_client_test: $(SERVER_REGISTERED_METHOD_BAD_CLIENT_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libbad_client_test.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LD) $(LDFLAGS) $(SERVER_REGISTERED_METHOD_BAD_CLIENT_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libbad_client_test.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) -o $(BINDIR)/$(CONFIG)/server_registered_method_bad_client_test
+
+$(OBJDIR)/$(CONFIG)/test/core/bad_client/tests/server_registered_method.o:  $(LIBDIR)/$(CONFIG)/libbad_client_test.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util_unsecure.a $(LIBDIR)/$(CONFIG)/libgrpc_unsecure.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+deps_server_registered_method_bad_client_test: $(SERVER_REGISTERED_METHOD_BAD_CLIENT_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_DEPS),true)
+-include $(SERVER_REGISTERED_METHOD_BAD_CLIENT_TEST_OBJS:.o=.dep)
+endif
+
+
 SIMPLE_REQUEST_BAD_CLIENT_TEST_SRC = \
     test/core/bad_client/tests/simple_request.c \
 
diff --git a/doc/PROTOCOL-HTTP2.md b/doc/PROTOCOL-HTTP2.md
index 7a26724..357ea72 100644
--- a/doc/PROTOCOL-HTTP2.md
+++ b/doc/PROTOCOL-HTTP2.md
@@ -45,7 +45,7 @@
 * **Custom-Metadata** → Binary-Header / ASCII-Header
 * **Binary-Header** → {Header-Name "-bin" } {_base64 encoded value_}
 * **ASCII-Header** → Header-Name ASCII-Value
-* **Header-Name** → 1\*( %x30-39 / %x61-7A / "\_" / "-") ; 0-9 a-z \_ -
+* **Header-Name** → 1\*( %x30-39 / %x61-7A / "\_" / "-" / ".") ; 0-9 a-z \_ - .
 * **ASCII-Value** → 1\*( %x20-%x7E ) ; space and printable ASCII
 
 
diff --git a/examples/cpp/helloworld/README.md b/examples/cpp/helloworld/README.md
index 7a7194d..90f3d6b 100644
--- a/examples/cpp/helloworld/README.md
+++ b/examples/cpp/helloworld/README.md
@@ -41,7 +41,7 @@
 a greeting in a single `HelloReply`. This is the simplest type of RPC you
 can specify in gRPC - we'll look at some other types later in this document.
 
-```
+```protobuf
 syntax = "proto3";
 
 option java_package = "ex.grpc";
@@ -93,20 +93,20 @@
   channel can be created with the target address, credentials to use and
   arguments as follows
 
-    ```
+    ```cpp
     auto channel = CreateChannel("localhost:50051", InsecureChannelCredentials());
     ```
 
 - Create a stub. A stub implements the rpc methods of a service and in the
   generated code, a method is provided to created a stub with a channel:
 
-    ```
+    ```cpp
     auto stub = helloworld::Greeter::NewStub(channel);
     ```
 
 - Make a unary rpc, with `ClientContext` and request/response proto messages.
 
-    ```
+    ```cpp
     ClientContext context;
     HelloRequest request;
     request.set_name("hello");
@@ -116,7 +116,7 @@
 
 - Check returned status and response.
 
-    ```
+    ```cpp
     if (status.ok()) {
       // check reply.message()
     } else {
@@ -130,7 +130,7 @@
 
 - Implement the service interface
 
-    ```
+    ```cpp
     class GreeterServiceImpl final : public Greeter::Service {
       Status SayHello(ServerContext* context, const HelloRequest* request,
           HelloReply* reply) override {
@@ -144,7 +144,7 @@
 
 - Build a server exporting the service
 
-    ```
+    ```cpp
     GreeterServiceImpl service;
     ServerBuilder builder;
     builder.AddListeningPort("0.0.0.0:50051", grpc::InsecureServerCredentials());
@@ -170,14 +170,14 @@
 - Initiate the rpc and create a handle for the rpc. Bind the rpc to a
   `CompletionQueue`.
 
-    ```
+    ```cpp
     CompletionQueue cq;
     auto rpc = stub->AsyncSayHello(&context, request, &cq);
     ```
 
 - Ask for reply and final status, with a unique tag
 
-    ```
+    ```cpp
     Status status;
     rpc->Finish(&reply, &status, (void*)1);
     ```
@@ -185,7 +185,7 @@
 - Wait for the completion queue to return the next tag. The reply and status are
   ready once the tag passed into the corresponding `Finish()` call is returned.
 
-    ```
+    ```cpp
     void* got_tag;
     bool ok = false;
     cq.Next(&got_tag, &ok);
@@ -203,7 +203,7 @@
 
 - Build a server exporting the async service
 
-    ```
+    ```cpp
     helloworld::Greeter::AsyncService service;
     ServerBuilder builder;
     builder.AddListeningPort("0.0.0.0:50051", InsecureServerCredentials());
@@ -214,7 +214,7 @@
 
 - Request one rpc
 
-    ```
+    ```cpp
     ServerContext context;
     HelloRequest request;
     ServerAsyncResponseWriter<HelloReply> responder;
@@ -224,7 +224,7 @@
 - Wait for the completion queue to return the tag. The context, request and
   responder are ready once the tag is retrieved.
 
-    ```
+    ```cpp
     HelloReply reply;
     Status status;
     void* got_tag;
@@ -239,7 +239,7 @@
 - Wait for the completion queue to return the tag. The rpc is finished when the
   tag is back.
 
-    ```
+    ```cpp
     void* got_tag;
     bool ok = false;
     cq.Next(&got_tag, &ok);
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 5d064ef..55aad9e 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -974,11 +974,19 @@
   batch_control *bctl = bctlp;
   grpc_call *call = bctl->call;
 
-  GPR_ASSERT(success);
-  gpr_slice_buffer_add(&(*call->receiving_buffer)->data.raw.slice_buffer,
-                       call->receiving_slice);
-
-  continue_receiving_slices(exec_ctx, bctl);
+  if (success) {
+    gpr_slice_buffer_add(&(*call->receiving_buffer)->data.raw.slice_buffer,
+                         call->receiving_slice);
+    continue_receiving_slices(exec_ctx, bctl);
+  } else {
+    grpc_byte_stream_destroy(call->receiving_stream);
+    call->receiving_stream = NULL;
+    grpc_byte_buffer_destroy(*call->receiving_buffer);
+    *call->receiving_buffer = NULL;
+    if (gpr_unref(&bctl->steps_to_complete)) {
+      post_batch_completion(exec_ctx, bctl);
+    }
+  }
 }
 
 static void finish_batch(grpc_exec_ctx *exec_ctx, void *bctlp, int success) {
@@ -1119,11 +1127,12 @@
     GRPC_CALL_INTERNAL_REF(call, "completion");
     bctl->success = 1;
     if (!is_notify_tag_closure) {
-      grpc_cq_begin_op(call->cq);
+      grpc_cq_begin_op(call->cq, notify_tag);
     }
     gpr_mu_unlock(&call->mu);
     post_batch_completion(exec_ctx, bctl);
-    return GRPC_CALL_OK;
+    error = GRPC_CALL_OK;
+    goto done;
   }
 
   /* rewrite batch ops into a transport op */
@@ -1333,7 +1342,7 @@
 
   GRPC_CALL_INTERNAL_REF(call, "completion");
   if (!is_notify_tag_closure) {
-    grpc_cq_begin_op(call->cq);
+    grpc_cq_begin_op(call->cq, notify_tag);
   }
   gpr_ref_init(&bctl->steps_to_complete, num_completion_callbacks_needed);
 
diff --git a/src/core/surface/channel_connectivity.c b/src/core/surface/channel_connectivity.c
index 57529ff..10f5c4d 100644
--- a/src/core/surface/channel_connectivity.c
+++ b/src/core/surface/channel_connectivity.c
@@ -189,7 +189,7 @@
       7, (channel, (int)last_observed_state, (long long)deadline.tv_sec,
           (int)deadline.tv_nsec, (int)deadline.clock_type, cq, tag));
 
-  grpc_cq_begin_op(cq);
+  grpc_cq_begin_op(cq, tag);
 
   gpr_mu_init(&w->mu);
   grpc_closure_init(&w->on_complete, watch_complete, w);
diff --git a/src/core/surface/channel_ping.c b/src/core/surface/channel_ping.c
index 1b6f06d..b4ce282 100644
--- a/src/core/surface/channel_ping.c
+++ b/src/core/surface/channel_ping.c
@@ -73,7 +73,7 @@
   grpc_closure_init(&pr->closure, ping_done, pr);
   op.send_ping = &pr->closure;
   op.bind_pollset = grpc_cq_pollset(cq);
-  grpc_cq_begin_op(cq);
+  grpc_cq_begin_op(cq, tag);
   top_elem->filter->start_transport_op(&exec_ctx, top_elem, &op);
   grpc_exec_ctx_finish(&exec_ctx);
 }
diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c
index 7704a83..848a33a 100644
--- a/src/core/surface/completion_queue.c
+++ b/src/core/surface/completion_queue.c
@@ -73,6 +73,12 @@
   plucker pluckers[GRPC_MAX_COMPLETION_QUEUE_PLUCKERS];
   grpc_closure pollset_shutdown_done;
 
+#ifndef NDEBUG
+  void **outstanding_tags;
+  size_t outstanding_tag_count;
+  size_t outstanding_tag_capacity;
+#endif
+
   grpc_completion_queue *next_free;
 };
 
@@ -89,6 +95,9 @@
   while (g_freelist) {
     grpc_completion_queue *next = g_freelist->next_free;
     grpc_pollset_destroy(&g_freelist->pollset);
+#ifndef NDEBUG
+    gpr_free(g_freelist->outstanding_tags);
+#endif
     gpr_free(g_freelist);
     g_freelist = next;
   }
@@ -117,6 +126,10 @@
 
     cc = gpr_malloc(sizeof(grpc_completion_queue));
     grpc_pollset_init(&cc->pollset);
+#ifndef NDEBUG
+    cc->outstanding_tags = NULL;
+    cc->outstanding_tag_capacity = 0;
+#endif
   } else {
     cc = g_freelist;
     g_freelist = g_freelist->next_free;
@@ -134,6 +147,9 @@
   cc->shutdown_called = 0;
   cc->is_server_cq = 0;
   cc->num_pluckers = 0;
+#ifndef NDEBUG
+  cc->outstanding_tag_count = 0;
+#endif
   grpc_closure_init(&cc->pollset_shutdown_done, on_pollset_shutdown_done, cc);
 
   GPR_TIMER_END("grpc_completion_queue_create", 0);
@@ -176,10 +192,17 @@
   }
 }
 
-void grpc_cq_begin_op(grpc_completion_queue *cc) {
+void grpc_cq_begin_op(grpc_completion_queue *cc, void *tag) {
 #ifndef NDEBUG
   gpr_mu_lock(GRPC_POLLSET_MU(&cc->pollset));
   GPR_ASSERT(!cc->shutdown_called);
+  if (cc->outstanding_tag_count == cc->outstanding_tag_capacity) {
+    cc->outstanding_tag_capacity = GPR_MAX(4, 2 * cc->outstanding_tag_capacity);
+    cc->outstanding_tags =
+        gpr_realloc(cc->outstanding_tags, sizeof(*cc->outstanding_tags) *
+                                              cc->outstanding_tag_capacity);
+  }
+  cc->outstanding_tags[cc->outstanding_tag_count++] = tag;
   gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset));
 #endif
   gpr_ref(&cc->pending_events);
@@ -196,6 +219,9 @@
   int shutdown;
   int i;
   grpc_pollset_worker *pluck_worker;
+#ifndef NDEBUG
+  int found = 0;
+#endif
 
   GPR_TIMER_BEGIN("grpc_cq_end_op", 0);
 
@@ -206,6 +232,18 @@
       ((gpr_uintptr)&cc->completed_head) | ((gpr_uintptr)(success != 0));
 
   gpr_mu_lock(GRPC_POLLSET_MU(&cc->pollset));
+#ifndef NDEBUG
+  for (i = 0; i < (int)cc->outstanding_tag_count; i++) {
+    if (cc->outstanding_tags[i] == tag) {
+      cc->outstanding_tag_count--;
+      GPR_SWAP(void *, cc->outstanding_tags[i],
+               cc->outstanding_tags[cc->outstanding_tag_count]);
+      found = 1;
+      break;
+    }
+  }
+  GPR_ASSERT(found);
+#endif
   shutdown = gpr_unref(&cc->pending_events);
   if (!shutdown) {
     cc->completed_tail->next =
diff --git a/src/core/surface/completion_queue.h b/src/core/surface/completion_queue.h
index a40bb04..1e40c48 100644
--- a/src/core/surface/completion_queue.h
+++ b/src/core/surface/completion_queue.h
@@ -68,10 +68,12 @@
 #endif
 
 /* Flag that an operation is beginning: the completion channel will not finish
-   shutdown until a corrensponding grpc_cq_end_* call is made */
-void grpc_cq_begin_op(grpc_completion_queue *cc);
+   shutdown until a corrensponding grpc_cq_end_* call is made.
+   \a tag is currently used only in debug builds. */
+void grpc_cq_begin_op(grpc_completion_queue *cc, void *tag);
 
-/* Queue a GRPC_OP_COMPLETED operation */
+/* Queue a GRPC_OP_COMPLETED operation; tag must correspond to the tag passed to
+   grpc_cq_begin_op */
 void grpc_cq_end_op(grpc_exec_ctx *exec_ctx, grpc_completion_queue *cc,
                     void *tag, int success,
                     void (*done)(grpc_exec_ctx *exec_ctx, void *done_arg,
diff --git a/src/core/surface/server.c b/src/core/surface/server.c
index cdbd542..1e1cde3 100644
--- a/src/core/surface/server.c
+++ b/src/core/surface/server.c
@@ -1007,7 +1007,7 @@
 
   /* lock, and gather up some stuff to do */
   gpr_mu_lock(&server->mu_global);
-  grpc_cq_begin_op(cq);
+  grpc_cq_begin_op(cq, tag);
   if (server->shutdown_published) {
     grpc_cq_end_op(&exec_ctx, cq, tag, 1, done_published_shutdown, NULL,
                    gpr_malloc(sizeof(grpc_cq_completion)));
@@ -1176,7 +1176,7 @@
     error = GRPC_CALL_ERROR_NOT_SERVER_COMPLETION_QUEUE;
     goto done;
   }
-  grpc_cq_begin_op(cq_for_notification);
+  grpc_cq_begin_op(cq_for_notification, tag);
   details->reserved = NULL;
   rc->type = BATCH_CALL;
   rc->server = server;
@@ -1213,7 +1213,7 @@
     error = GRPC_CALL_ERROR_NOT_SERVER_COMPLETION_QUEUE;
     goto done;
   }
-  grpc_cq_begin_op(cq_for_notification);
+  grpc_cq_begin_op(cq_for_notification, tag);
   rc->type = REGISTERED_CALL;
   rc->server = server;
   rc->tag = tag;
diff --git a/src/core/transport/chttp2/frame_data.c b/src/core/transport/chttp2/frame_data.c
index 732124b..60a3ce2 100644
--- a/src/core/transport/chttp2/frame_data.c
+++ b/src/core/transport/chttp2/frame_data.c
@@ -53,7 +53,8 @@
                                      grpc_chttp2_data_parser *parser) {
   grpc_byte_stream *bs;
   if (parser->parsing_frame) {
-    grpc_chttp2_incoming_byte_stream_finished(exec_ctx, parser->parsing_frame);
+    grpc_chttp2_incoming_byte_stream_finished(exec_ctx, parser->parsing_frame,
+                                              0, 1);
   }
   while (
       (bs = grpc_chttp2_incoming_frame_queue_pop(&parser->incoming_frames))) {
@@ -218,7 +219,8 @@
         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_chttp2_incoming_byte_stream_finished(exec_ctx, p->parsing_frame, 1,
+                                                  1);
         p->parsing_frame = NULL;
         p->state = GRPC_CHTTP2_DATA_FH_0;
         return GRPC_CHTTP2_PARSE_OK;
@@ -227,7 +229,8 @@
             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_chttp2_incoming_byte_stream_finished(exec_ctx, p->parsing_frame, 1,
+                                                  1);
         p->parsing_frame = NULL;
         cur += p->frame_size;
         goto fh_0; /* loop */
diff --git a/src/core/transport/chttp2/internal.h b/src/core/transport/chttp2/internal.h
index fc35ea6..4ad9003 100644
--- a/src/core/transport/chttp2/internal.h
+++ b/src/core/transport/chttp2/internal.h
@@ -65,6 +65,7 @@
   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 that are waiting to start because there are too many concurrent
       streams on the connection */
@@ -151,6 +152,7 @@
   grpc_byte_stream base;
   gpr_refcount refs;
   struct grpc_chttp2_incoming_byte_stream *next_message;
+  int failed;
 
   grpc_chttp2_transport *transport;
   grpc_chttp2_stream *stream;
@@ -391,8 +393,6 @@
   gpr_uint8 write_closed;
   /** is this stream reading half-closed (boolean) */
   gpr_uint8 read_closed;
-  /** is this stream finished closing (and reportably closed) */
-  gpr_uint8 finished_close;
   /** is this stream in the stream map? (boolean) */
   gpr_uint8 in_stream_map;
   /** has this stream seen an error? if 1, then pending incoming frames
@@ -586,6 +586,13 @@
     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, gpr_uint32 id);
 grpc_chttp2_stream_parsing *grpc_chttp2_parsing_accept_stream(
@@ -742,7 +749,8 @@
                                            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_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs, int success,
+    int from_parsing_thread);
 
 void grpc_chttp2_ack_ping(grpc_exec_ctx *exec_ctx,
                           grpc_chttp2_transport_parsing *parsing,
diff --git a/src/core/transport/chttp2/stream_lists.c b/src/core/transport/chttp2/stream_lists.c
index a4c85b4..49f951d 100644
--- a/src/core/transport/chttp2/stream_lists.c
+++ b/src/core/transport/chttp2/stream_lists.c
@@ -353,6 +353,26 @@
   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);
diff --git a/src/core/transport/chttp2/writing.c b/src/core/transport/chttp2/writing.c
index 805d052..b5ca42d 100644
--- a/src/core/transport/chttp2/writing.c
+++ b/src/core/transport/chttp2/writing.c
@@ -332,17 +332,12 @@
 
   while (grpc_chttp2_list_pop_written_stream(
       transport_global, transport_writing, &stream_global, &stream_writing)) {
-    if (stream_writing->sent_trailing_metadata) {
-      grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global,
-                                     !transport_global->is_client, 1);
-    }
     if (stream_writing->sent_initial_metadata) {
       grpc_chttp2_complete_closure_step(
           exec_ctx, &stream_global->send_initial_metadata_finished, 1);
     }
     if (stream_writing->sent_message) {
       GPR_ASSERT(stream_writing->send_message == NULL);
-      GPR_ASSERT(stream_global->send_message_finished);
       grpc_chttp2_complete_closure_step(
           exec_ctx, &stream_global->send_message_finished, 1);
       stream_writing->sent_message = 0;
@@ -351,6 +346,10 @@
       grpc_chttp2_complete_closure_step(
           exec_ctx, &stream_global->send_trailing_metadata_finished, 1);
     }
+    if (stream_writing->sent_trailing_metadata) {
+      grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global,
+                                     !transport_global->is_client, 1);
+    }
     GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2_writing");
   }
   gpr_slice_buffer_reset_and_unref(&transport_writing->outbuf);
diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c
index aa459c8..c3847bb 100644
--- a/src/core/transport/chttp2_transport.c
+++ b/src/core/transport/chttp2_transport.c
@@ -134,6 +134,9 @@
 static void check_read_ops(grpc_exec_ctx *exec_ctx,
                            grpc_chttp2_transport_global *transport_global);
 
+static void fail_pending_writes(grpc_exec_ctx *exec_ctx, 
+                                grpc_chttp2_stream_global *stream_global);
+
 /*
  * CONSTRUCTION/DESTRUCTION/REFCOUNTING
  */
@@ -625,6 +628,7 @@
                                    void *transport_writing_ptr, int success) {
   grpc_chttp2_transport_writing *transport_writing = transport_writing_ptr;
   grpc_chttp2_transport *t = TRANSPORT_FROM_WRITING(transport_writing);
+  grpc_chttp2_stream_global *stream_global;
 
   GPR_TIMER_BEGIN("grpc_chttp2_terminate_writing", 0);
 
@@ -638,6 +642,11 @@
 
   grpc_chttp2_cleanup_writing(exec_ctx, &t->global, &t->writing);
 
+  while (grpc_chttp2_list_pop_closed_waiting_for_writing(&t->global, &stream_global)) {
+    fail_pending_writes(exec_ctx, stream_global);
+    GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "finish_writes");
+  }
+
   /* leave the writing flag up on shutdown to prevent further writes in unlock()
      from starting */
   t->writing_active = 0;
@@ -1040,6 +1049,12 @@
     t->parsing.incoming_stream = NULL;
     grpc_chttp2_parsing_become_skip_parser(exec_ctx, &t->parsing);
   }
+  if (s->parsing.data_parser.parsing_frame != NULL) {
+    grpc_chttp2_incoming_byte_stream_finished(
+        exec_ctx, s->parsing.data_parser.parsing_frame, 0, 0);
+    s->parsing.data_parser.parsing_frame = NULL;
+  }
+
   if (grpc_chttp2_unregister_stream(t, s) && t->global.sent_goaway) {
     close_transport_locked(exec_ctx, t);
   }
@@ -1107,6 +1122,16 @@
   }
 }
 
+static void fail_pending_writes(grpc_exec_ctx *exec_ctx, 
+                                grpc_chttp2_stream_global *stream_global) {
+  grpc_chttp2_complete_closure_step(
+      exec_ctx, &stream_global->send_initial_metadata_finished, 0);
+  grpc_chttp2_complete_closure_step(
+      exec_ctx, &stream_global->send_trailing_metadata_finished, 0);
+  grpc_chttp2_complete_closure_step(exec_ctx,
+                                    &stream_global->send_message_finished, 0);
+}
+
 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,
@@ -1123,6 +1148,13 @@
   }
   if (close_writes && !stream_global->write_closed) {
     stream_global->write_closed = 1;
+    if (TRANSPORT_FROM_GLOBAL(transport_global)->writing_active) {
+      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, stream_global);
+    }
   }
   if (stream_global->read_closed && stream_global->write_closed) {
     if (stream_global->id != 0 &&
@@ -1134,7 +1166,6 @@
         remove_stream(exec_ctx, TRANSPORT_FROM_GLOBAL(transport_global),
                       stream_global->id);
       }
-      stream_global->finished_close = 1;
       GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2");
     }
   }
@@ -1348,7 +1379,6 @@
       GPR_ASSERT(stream_global->write_closed);
       GPR_ASSERT(stream_global->read_closed);
       remove_stream(exec_ctx, t, stream_global->id);
-      stream_global->finished_close = 1;
       GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2");
     }
   }
@@ -1479,6 +1509,10 @@
     *slice = gpr_slice_buffer_take_first(&bs->slices);
     unlock(exec_ctx, bs->transport);
     return 1;
+  } else if (bs->failed) {
+    grpc_exec_ctx_enqueue(exec_ctx, on_complete, 0);
+    unlock(exec_ctx, bs->transport);
+    return 0;
   } else {
     bs->on_next = on_complete;
     bs->next = slice;
@@ -1513,7 +1547,29 @@
 }
 
 void grpc_chttp2_incoming_byte_stream_finished(
-    grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs) {
+    grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs, int success,
+    int from_parsing_thread) {
+  if (!success) {
+    if (from_parsing_thread) {
+      gpr_mu_lock(&bs->transport->mu);
+    }
+    grpc_exec_ctx_enqueue(exec_ctx, bs->on_next, 0);
+    bs->on_next = NULL;
+    bs->failed = 1;
+    if (from_parsing_thread) {
+      gpr_mu_unlock(&bs->transport->mu);
+    }
+  } else {
+#ifndef NDEBUG
+    if (from_parsing_thread) {
+      gpr_mu_lock(&bs->transport->mu);
+    }
+    GPR_ASSERT(bs->on_next == NULL);
+    if (from_parsing_thread) {
+      gpr_mu_unlock(&bs->transport->mu);
+    }
+#endif
+  }
   incoming_byte_stream_unref(bs);
 }
 
@@ -1534,6 +1590,7 @@
   gpr_slice_buffer_init(&incoming_byte_stream->slices);
   incoming_byte_stream->on_next = NULL;
   incoming_byte_stream->is_tail = 1;
+  incoming_byte_stream->failed = 0;
   if (add_to_queue->head == NULL) {
     add_to_queue->head = incoming_byte_stream;
   } else {
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index a171855..475c792 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -91,6 +91,7 @@
     <Compile Include="ContextPropagationTest.cs" />
     <Compile Include="MetadataTest.cs" />
     <Compile Include="PerformanceTest.cs" />
+    <Compile Include="SanityTest.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
index 48b5d78..74f7f24 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
@@ -167,18 +167,18 @@
                 () => Timespec.FromDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)));
         }
             
-        // Test attribute commented out to prevent running as part of the default test suite.
-        // [Test]
-        // [Category("Performance")]
+        [Test]
+        [Category("Performance")]
+        [Ignore("Prevent running on Jenkins")]
         public void NowBenchmark() 
         {
             // approx Timespec.Now latency <33ns
             BenchmarkUtil.RunBenchmark(10000000, 1000000000, () => { var now = Timespec.Now; });
         }
-
-        // Test attribute commented out to prevent running as part of the default test suite.
-        // [Test]
-        // [Category("Performance")]
+            
+        [Test]
+        [Category("Performance")]
+        [Ignore("Prevent running on Jenkins")]
         public void PreciseNowBenchmark()
         {
             // approx Timespec.PreciseNow latency <18ns (when compiled with GRPC_TIMERS_RDTSC)
diff --git a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
index 073c502..af55cb0 100644
--- a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
+++ b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
@@ -59,6 +59,8 @@
         [Test]
         public void CompletionQueueCreateDestroyBenchmark()
         {
+            GrpcEnvironment.AddRef();  // completion queue requires gRPC environment being initialized.
+
             BenchmarkUtil.RunBenchmark(
                 10, 10,
                 () =>
@@ -66,6 +68,8 @@
                     CompletionQueueSafeHandle cq = CompletionQueueSafeHandle.Create();
                     cq.Dispose();
                 });
+
+            GrpcEnvironment.Release();
         }
 
         /// <summary>
diff --git a/src/csharp/Grpc.Core.Tests/PerformanceTest.cs b/src/csharp/Grpc.Core.Tests/PerformanceTest.cs
index 5516cd3..6815839 100644
--- a/src/csharp/Grpc.Core.Tests/PerformanceTest.cs
+++ b/src/csharp/Grpc.Core.Tests/PerformanceTest.cs
@@ -67,10 +67,10 @@
             channel.ShutdownAsync().Wait();
             server.ShutdownAsync().Wait();
         }
-
-        // Test attribute commented out to prevent running as part of the default test suite.
-        //[Test]
-        //[Category("Performance")]
+            
+        [Test]
+        [Category("Performance")]
+        [Ignore("Prevent running on Jenkins")]
         public void UnaryCallPerformance()
         {
             var profiler = new BasicProfiler();
diff --git a/src/csharp/Grpc.Core.Tests/SanityTest.cs b/src/csharp/Grpc.Core.Tests/SanityTest.cs
new file mode 100644
index 0000000..343ab1e
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/SanityTest.cs
@@ -0,0 +1,125 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+    public class SanityTest
+    {
+        /// <summary>
+        /// Because we depend on a native library, sometimes when things go wrong, the
+        /// entire NUnit test process crashes. To be able to track down problems better,
+        /// the NUnit tests are run by run_tests.py script in a separate process per test class.
+        /// The list of tests to run is stored in src/csharp/tests.json.
+        /// This test checks that the tests.json file is up to date by discovering all the
+        /// existing NUnit tests in all test assemblies and comparing to contents of tests.json.
+        /// </summary>
+        [Test]
+        public void TestsJsonUpToDate()
+        {
+            var testClasses = DiscoverAllTestClasses();
+            string testsJson = GetTestsJson();
+
+            // we don't have a JSON parser at hand, but check that the test class
+            // name is contained in the file instead.
+            foreach (var className in testClasses) {
+                Assert.IsTrue(testsJson.Contains(className),
+                    string.Format("Test class \"{0}\" is missing in C# tests.json file", className));
+            }
+        }
+
+        /// <summary>
+        /// Gets list of all test classes obtained by inspecting all the test assemblies.
+        /// </summary>
+        private List<string> DiscoverAllTestClasses()
+        {
+            var assemblies = GetTestAssemblies();
+
+            var testClasses = new List<string>();
+            foreach (var assembly in assemblies)
+            {
+                foreach (var t in assembly.GetTypes())
+                {
+                    foreach (var m in t.GetMethods())
+                    {
+                        var attributes = m.GetCustomAttributes(typeof(NUnit.Framework.TestAttribute), true);
+                        if (attributes.Length > 0)
+                        {
+                            testClasses.Add(t.FullName);
+                            break;
+                        }
+
+                    }
+                }
+            }
+            testClasses.Sort();
+            return testClasses;
+        }
+
+        private string GetTestsJson()
+        {
+            var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+            var testsJsonFile = Path.Combine(assemblyDir, "..", "..", "..", "tests.json");
+
+            return File.ReadAllText(testsJsonFile);
+        }
+
+        private List<Assembly> GetTestAssemblies()
+        {
+            var result = new List<Assembly>();
+            var executingAssembly = Assembly.GetExecutingAssembly();
+
+            result.Add(executingAssembly);
+
+            var otherAssemblies = new[] {
+                "Grpc.Examples.Tests",
+                "Grpc.HealthCheck.Tests",
+                "Grpc.IntegrationTesting"
+            };
+            foreach (var assemblyName in otherAssemblies)
+            {
+                var location = executingAssembly.Location.Replace("Grpc.Core.Tests", assemblyName);
+                result.Add(Assembly.LoadFrom(location));
+            }
+            return result;
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ShutdownTest.cs b/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
index a2be7dd..10d666d 100644
--- a/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
@@ -61,17 +61,20 @@
         }
 
         [Test]
-        public async Task AbandonedCall()
+        public async Task AbandonedCall_ServerKillAsync()
         {
+            var readyToShutdown = new TaskCompletionSource<object>();
             helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
             {
+                readyToShutdown.SetResult(null);
                 await requestStream.ToListAsync();
             });
 
-            var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddMilliseconds(1))));
+            var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall());
+            await readyToShutdown.Task;  // make sure handler is running
 
-            channel.ShutdownAsync().Wait();
-            server.ShutdownAsync().Wait();
+            await channel.ShutdownAsync();  // channel.ShutdownAsync() works even if there's a pending call.
+            await server.KillAsync();  // server.ShutdownAsync() would hang waiting for the call to finish.
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index 0fadabe..d120f95 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -171,6 +171,8 @@
             handle.CancelAllCalls();
             await shutdownTcs.Task.ConfigureAwait(false);
             DisposeHandle();
+
+            await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false);
         }
 
         internal void AddCallReference(object call)
diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
index c48ac71..b0d920a 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -41,6 +41,9 @@
     <Reference Include="CommandLine">
       <HintPath>..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll</HintPath>
     </Reference>
+    <Reference Include="Moq">
+      <HintPath>..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
+    </Reference>
     <Reference Include="nunit.framework">
       <HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
     </Reference>
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
index 5facb87..18168f9 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
@@ -140,12 +140,14 @@
         }
 
         [Test]
+        [Ignore("TODO: see #4427")]
         public async Task StatusCodeAndMessage()
         {
             await InteropClient.RunStatusCodeAndMessageAsync(client);
         }
 
         [Test]
+        [Ignore("TODO: see #4427")]
         public void UnimplementedMethod()
         {
             InteropClient.RunUnimplementedMethod(UnimplementedService.NewClient(channel));
diff --git a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
index 35230f4..1c8bfed 100644
--- a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
@@ -40,6 +40,7 @@
 using Grpc.Core;
 using Grpc.Core.Utils;
 using Grpc.Testing;
+using Moq;
 using NUnit.Framework;
 
 namespace Grpc.IntegrationTesting
@@ -50,37 +51,37 @@
         Server server;
         Channel channel;
         TestService.ITestServiceClient client;
+        List<ChannelOption> options;
+        Mock<TestService.ITestService> serviceMock;
+        AsyncAuthInterceptor asyncAuthInterceptor;
 
-        [TestFixtureSetUp]
+        [SetUp]
         public void Init()
         {
-            var serverCredentials = new SslServerCredentials(new[] { new KeyCertificatePair(File.ReadAllText(TestCredentials.ServerCertChainPath), File.ReadAllText(TestCredentials.ServerPrivateKeyPath)) });
+            serviceMock = new Mock<TestService.ITestService>();
+            serviceMock.Setup(m => m.UnaryCall(It.IsAny<SimpleRequest>(), It.IsAny<ServerCallContext>()))
+                .Returns(new Func<SimpleRequest, ServerCallContext, Task<SimpleResponse>>(UnaryCallHandler));
+
             server = new Server
             {
-                Services = { TestService.BindService(new TestServiceImpl()) },
-                Ports = { { Host, ServerPort.PickUnused, serverCredentials } }
+                Services = { TestService.BindService(serviceMock.Object) },
+                Ports = { { Host, ServerPort.PickUnused, TestCredentials.CreateSslServerCredentials() } }
             };
             server.Start();
 
-            var options = new List<ChannelOption>
+            options = new List<ChannelOption>
             {
                 new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
             };
 
-            var asyncAuthInterceptor = new AsyncAuthInterceptor(async (context, metadata) =>
+            asyncAuthInterceptor = new AsyncAuthInterceptor(async (context, metadata) =>
             {
-                await Task.Delay(100);  // make sure the operation is asynchronous.
+                await Task.Delay(100).ConfigureAwait(false);  // make sure the operation is asynchronous.
                 metadata.Add("authorization", "SECRET_TOKEN");
             });
-
-            var clientCredentials = ChannelCredentials.Create(
-                new SslCredentials(File.ReadAllText(TestCredentials.ClientCertAuthorityPath)),
-                CallCredentials.FromInterceptor(asyncAuthInterceptor));
-            channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options);
-            client = TestService.NewClient(channel);
         }
 
-        [TestFixtureTearDown]
+        [TearDown]
         public void Cleanup()
         {
             channel.ShutdownAsync().Wait();
@@ -90,8 +91,29 @@
         [Test]
         public void MetadataCredentials()
         {
-            var response = client.UnaryCall(new SimpleRequest { ResponseSize = 10 });
-            Assert.AreEqual(10, response.Payload.Body.Length);
+            var channelCredentials = ChannelCredentials.Create(TestCredentials.CreateSslCredentials(),
+                CallCredentials.FromInterceptor(asyncAuthInterceptor));
+            channel = new Channel(Host, server.Ports.Single().BoundPort, channelCredentials, options);
+            client = TestService.NewClient(channel);
+
+            client.UnaryCall(new SimpleRequest {});
+        }
+
+        [Test]
+        public void MetadataCredentials_PerCall()
+        {
+            channel = new Channel(Host, server.Ports.Single().BoundPort, TestCredentials.CreateSslCredentials(), options);
+            client = TestService.NewClient(channel);
+
+            var callCredentials = CallCredentials.FromInterceptor(asyncAuthInterceptor);
+            client.UnaryCall(new SimpleRequest { }, new CallOptions(credentials: callCredentials));
+        }
+
+        private Task<SimpleResponse> UnaryCallHandler(SimpleRequest request, ServerCallContext context)
+        {
+            var authToken = context.RequestHeaders.First((entry) => entry.Key == "authorization").Value;
+            Assert.AreEqual("SECRET_TOKEN", authToken);
+            return Task.FromResult(new SimpleResponse());
         }
     }
 }
diff --git a/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs
index 2b51526..3dd91b7 100644
--- a/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs
@@ -75,9 +75,10 @@
             serverRunner.StopAsync().Wait();
         }
 
-        // Test attribute commented out to prevent running as part of the default test suite.
-        //[Test]
-        //[Category("Performance")]
+
+        [Test]
+        [Category("Performance")]
+        [Ignore("Prevent running on Jenkins")]
         public async Task ClientServerRunner()
         {
             var config = new ClientConfig
diff --git a/src/csharp/Grpc.IntegrationTesting/packages.config b/src/csharp/Grpc.IntegrationTesting/packages.config
index bdb3dad..5c59af1 100644
--- a/src/csharp/Grpc.IntegrationTesting/packages.config
+++ b/src/csharp/Grpc.IntegrationTesting/packages.config
@@ -11,6 +11,7 @@
   <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" />
   <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
   <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Moq" version="4.2.1510.2205" targetFramework="net45" />
   <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
   <package id="NUnit" version="2.6.4" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/src/csharp/tests.json b/src/csharp/tests.json
new file mode 100644
index 0000000..4aa9366
--- /dev/null
+++ b/src/csharp/tests.json
@@ -0,0 +1,45 @@
+{
+  "assemblies": [
+    "Grpc.Core.Tests",
+    "Grpc.Examples.Tests",
+    "Grpc.HealthCheck.Tests",
+    "Grpc.IntegrationTesting"
+  ],
+  "tests": [
+    "Grpc.Core.Internal.Tests.AsyncCallTest",
+    "Grpc.Core.Internal.Tests.ChannelArgsSafeHandleTest",
+    "Grpc.Core.Internal.Tests.CompletionQueueEventTest",
+    "Grpc.Core.Internal.Tests.CompletionQueueSafeHandleTest",
+    "Grpc.Core.Internal.Tests.MetadataArraySafeHandleTest",
+    "Grpc.Core.Internal.Tests.TimespecTest",
+    "Grpc.Core.Tests.CallCredentialsTest",
+    "Grpc.Core.Tests.CallOptionsTest",
+    "Grpc.Core.Tests.ChannelCredentialsTest",
+    "Grpc.Core.Tests.ChannelOptionsTest",
+    "Grpc.Core.Tests.ChannelTest",
+    "Grpc.Core.Tests.ClientServerTest",
+    "Grpc.Core.Tests.CompressionTest",
+    "Grpc.Core.Tests.ContextPropagationTest",
+    "Grpc.Core.Tests.GrpcEnvironmentTest",
+    "Grpc.Core.Tests.MarshallingErrorsTest",
+    "Grpc.Core.Tests.MetadataTest",
+    "Grpc.Core.Tests.NUnitVersionTest",
+    "Grpc.Core.Tests.PerformanceTest",
+    "Grpc.Core.Tests.PInvokeTest",
+    "Grpc.Core.Tests.ResponseHeadersTest",
+    "Grpc.Core.Tests.SanityTest",
+    "Grpc.Core.Tests.ServerTest",
+    "Grpc.Core.Tests.ShutdownTest",
+    "Grpc.Core.Tests.TimeoutsTest",
+    "Grpc.Core.Tests.UserAgentStringTest",
+    "Math.Tests.MathClientServerTest",
+    "Grpc.HealthCheck.Tests.HealthClientServerTest",
+    "Grpc.HealthCheck.Tests.HealthServiceImplTest",
+    "Grpc.IntegrationTesting.HeaderInterceptorTest",
+    "Grpc.IntegrationTesting.HistogramTest",
+    "Grpc.IntegrationTesting.InteropClientServerTest",
+    "Grpc.IntegrationTesting.MetadataCredentialsTest",
+    "Grpc.IntegrationTesting.RunnerClientServerTest",
+    "Grpc.IntegrationTesting.SslCredentialsTest"
+  ]
+}
\ No newline at end of file
diff --git a/src/node/interop/async_delay_queue.js b/src/node/interop/async_delay_queue.js
new file mode 100644
index 0000000..2bd3ca4
--- /dev/null
+++ b/src/node/interop/async_delay_queue.js
@@ -0,0 +1,79 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+'use strict';
+
+var _ = require('lodash');
+
+/**
+ * This class represents a queue of callbacks that must happen sequentially, each
+ * with a specific delay after the previous event.
+ */
+function AsyncDelayQueue() {
+  this.queue = [];
+
+  this.callback_pending = false;
+}
+
+/**
+ * Run the next callback after its corresponding delay, if there are any
+ * remaining.
+ */
+AsyncDelayQueue.prototype.runNext = function() {
+  var next = this.queue.shift();
+  var continueCallback = _.bind(this.runNext, this);
+  if (next) {
+    this.callback_pending = true;
+    setTimeout(function() {
+      next.callback(continueCallback);
+    }, next.delay);
+  } else {
+    this.callback_pending = false;
+  }
+};
+
+/**
+ * Add a callback to be called with a specific delay after now or after the
+ * current last item in the queue or current pending callback, whichever is
+ * latest.
+ * @param {function(function())} callback The callback
+ * @param {Number} The delay to apply, in milliseconds
+ */
+AsyncDelayQueue.prototype.add = function(callback, delay) {
+  this.queue.push({callback: callback, delay: delay});
+  if (!this.callback_pending) {
+    this.runNext();
+  }
+};
+
+module.exports = AsyncDelayQueue;
diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js
index 5321005..9526b5d 100644
--- a/src/node/interop/interop_server.js
+++ b/src/node/interop/interop_server.js
@@ -36,6 +36,7 @@
 var fs = require('fs');
 var path = require('path');
 var _ = require('lodash');
+var AsyncDelayQueue = require('./async_delay_queue');
 var grpc = require('..');
 var testProto = grpc.load({
   root: __dirname + '/../../..',
@@ -155,6 +156,7 @@
  */
 function handleStreamingOutput(call) {
   echoHeader(call);
+  var delay_queue = new AsyncDelayQueue();
   var req = call.request;
   if (req.response_status) {
     var status = req.response_status;
@@ -163,9 +165,15 @@
     return;
   }
   _.each(req.response_parameters, function(resp_param) {
-    call.write({payload: getPayload(req.response_type, resp_param.size)});
+    delay_queue.add(function(next) {
+      call.write({payload: getPayload(req.response_type, resp_param.size)});
+      next();
+    }, resp_param.interval_us);
   });
-  call.end(getEchoTrailer(call));
+  delay_queue.add(function(next) {
+    call.end(getEchoTrailer(call));
+    next();
+  });
 }
 
 /**
@@ -175,6 +183,7 @@
  */
 function handleFullDuplex(call) {
   echoHeader(call);
+  var delay_queue = new AsyncDelayQueue();
   call.on('data', function(value) {
     if (value.response_status) {
       var status = value.response_status;
@@ -183,11 +192,17 @@
       return;
     }
     _.each(value.response_parameters, function(resp_param) {
-      call.write({payload: getPayload(value.response_type, resp_param.size)});
+      delay_queue.add(function(next) {
+        call.write({payload: getPayload(value.response_type, resp_param.size)});
+        next();
+      }, resp_param.interval_us);
     });
   });
   call.on('end', function() {
-    call.end(getEchoTrailer(call));
+    delay_queue.add(function(next) {
+      call.end(getEchoTrailer(call));
+      next();
+    });
   });
 }
 
diff --git a/src/php/ext/grpc/package.xml b/src/php/ext/grpc/package.xml
index a2e3e28..79a0a79 100644
--- a/src/php/ext/grpc/package.xml
+++ b/src/php/ext/grpc/package.xml
@@ -10,11 +10,11 @@
   <email>grpc-packages@google.com</email>
   <active>yes</active>
  </lead>
- <date>2015-10-21</date>
- <time>17:04:32</time>
+ <date>2015-12-15</date>
+ <time>13:49:29</time>
  <version>
-  <release>0.6.1</release>
-  <api>0.6.0</api>
+  <release>0.7.0</release>
+  <api>0.7.0</api>
  </version>
  <stability>
   <release>beta</release>
@@ -22,19 +22,21 @@
  </stability>
  <license>BSD</license>
  <notes>
-- fixed undefined constant fatal error when run with apache/nginx #2275
+- Breaking change to Credentials class (removed) #3765
+- Replaced by ChannelCredentials and CallCredentials class #3765
+- New plugin based metadata auth API #4394
  </notes>
  <contents>
   <dir baseinstalldir="/" name="/">
    <file baseinstalldir="/" md5sum="6f19828fb869b7b8a590cbb76b4f996d" name="byte_buffer.c" role="src" />
    <file baseinstalldir="/" md5sum="c8de0f819499c48adfc8d7f472c0196b" name="byte_buffer.h" role="src" />
-   <file baseinstalldir="/" md5sum="d64c9005993de02abac55664b0b9e0b2" name="call.c" role="src" />
-   <file baseinstalldir="/" md5sum="26acbf04c30162c2d2aca4688bb2aec8" name="call.h" role="src" />
-   <file baseinstalldir="/" md5sum="6fa13d260dfde216f795225644f04e7a" name="call_credentials.c" role="src" />
-   <file baseinstalldir="/" md5sum="e45269975f9a30fd349a90daf6b31aa2" name="call_credentials.h" role="src" />
-   <file baseinstalldir="/" md5sum="0779db3b196c98081b2260ceec22cd4d" name="channel.c" role="src" />
+   <file baseinstalldir="/" md5sum="ee7eb7757f9e6f0e36f8f616b6bd0af5" name="call.c" role="src" />
+   <file baseinstalldir="/" md5sum="44c56bd9912d2538cbd6059e3e0452b6" name="call.h" role="src" />
+   <file baseinstalldir="/" md5sum="ff90f6c03ed44b5f4170bf3259a6704e" name="call_credentials.c" role="src" />
+   <file baseinstalldir="/" md5sum="3c3860e1d84f43cb6b2fbaa8d2ae1ab7" name="call_credentials.h" role="src" />
+   <file baseinstalldir="/" md5sum="00b44de389fbafa68afe8173045940af" name="channel.c" role="src" />
    <file baseinstalldir="/" md5sum="ed4b00c0cf3702b115d0cfa87450dc09" name="channel.h" role="src" />
-   <file baseinstalldir="/" md5sum="1b40f50fa6184ad7d24a961ac76151ff" name="channel_credentials.c" role="src" />
+   <file baseinstalldir="/" md5sum="d6e32503492b22bca1baf119d6c5e373" name="channel_credentials.c" role="src" />
    <file baseinstalldir="/" md5sum="a86250e03f610ce6c2c7595a84e08821" name="channel_credentials.h" role="src" />
    <file baseinstalldir="/" md5sum="55ab7a42f9dd9bfc7e28a61cfc5fca63" name="completion_queue.c" role="src" />
    <file baseinstalldir="/" md5sum="f10b5bb232d74a6878e829e2e76cdaa2" name="completion_queue.h" role="src" />
@@ -130,5 +132,22 @@
 - fixed undefined constant fatal error when run with apache/nginx #2275
    </notes>
   </release>
+  <release>
+   <version>
+    <release>0.7.0</release>
+    <api>0.7.0</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2015-12-15</date>
+   <license>BSD</license>
+   <notes>
+- Breaking change to Credentials class (removed) #3765
+- Replaced by ChannelCredentials and CallCredentials class #3765
+- New plugin based metadata auth API #4394
+   </notes>
+  </release>
  </changelog>
 </package>
diff --git a/test/core/bad_client/bad_client.c b/test/core/bad_client/bad_client.c
index e1a4b8e..5aceac6 100644
--- a/test/core/bad_client/bad_client.c
+++ b/test/core/bad_client/bad_client.c
@@ -49,13 +49,14 @@
   grpc_server *server;
   grpc_completion_queue *cq;
   grpc_bad_client_server_side_validator validator;
+  void *registered_method;
   gpr_event done_thd;
   gpr_event done_write;
 } thd_args;
 
 static void thd_func(void *arg) {
   thd_args *a = arg;
-  a->validator(a->server, a->cq);
+  a->validator(a->server, a->cq, a->registered_method);
   gpr_event_set(&a->done_thd, (void *)1);
 }
 
@@ -110,6 +111,9 @@
   gpr_event_init(&a.done_write);
   a.validator = validator;
   grpc_server_register_completion_queue(a.server, a.cq, NULL);
+  a.registered_method =
+      grpc_server_register_method(a.server, GRPC_BAD_CLIENT_REGISTERED_METHOD,
+                                  GRPC_BAD_CLIENT_REGISTERED_HOST);
   grpc_server_start(a.server);
   transport = grpc_create_chttp2_transport(&exec_ctx, NULL, sfd.server, 0);
   server_setup_transport(&a, transport);
diff --git a/test/core/bad_client/bad_client.h b/test/core/bad_client/bad_client.h
index 01beda6..2dd100a 100644
--- a/test/core/bad_client/bad_client.h
+++ b/test/core/bad_client/bad_client.h
@@ -37,8 +37,12 @@
 #include <grpc/grpc.h>
 #include "test/core/util/test_config.h"
 
-typedef void (*grpc_bad_client_server_side_validator)(
-    grpc_server *server, grpc_completion_queue *cq);
+#define GRPC_BAD_CLIENT_REGISTERED_METHOD "/registered/bar"
+#define GRPC_BAD_CLIENT_REGISTERED_HOST "localhost"
+
+typedef void (*grpc_bad_client_server_side_validator)(grpc_server *server,
+                                                      grpc_completion_queue *cq,
+                                                      void *registered_method);
 
 #define GRPC_BAD_CLIENT_DISCONNECT 1
 
diff --git a/test/core/bad_client/gen_build_yaml.py b/test/core/bad_client/gen_build_yaml.py
index 386a896..45fcb69 100755
--- a/test/core/bad_client/gen_build_yaml.py
+++ b/test/core/bad_client/gen_build_yaml.py
@@ -44,6 +44,7 @@
     'connection_prefix': default_test_options,
     'headers': default_test_options,
     'initial_settings_frame': default_test_options,
+    'server_registered_method': default_test_options,
     'simple_request': default_test_options,
     'unknown_frame': default_test_options,
 }
diff --git a/test/core/bad_client/tests/badreq.c b/test/core/bad_client/tests/badreq.c
index f0d7dac..6d59d25 100644
--- a/test/core/bad_client/tests/badreq.c
+++ b/test/core/bad_client/tests/badreq.c
@@ -42,7 +42,8 @@
   "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" \
   "\x00\x00\x00\x04\x00\x00\x00\x00\x00" /* settings frame */
 
-static void verifier(grpc_server *server, grpc_completion_queue *cq) {
+static void verifier(grpc_server *server, grpc_completion_queue *cq,
+                     void *registered_method) {
   while (grpc_server_has_open_connections(server)) {
     GPR_ASSERT(grpc_completion_queue_next(cq,
                                           GRPC_TIMEOUT_MILLIS_TO_DEADLINE(20),
diff --git a/test/core/bad_client/tests/connection_prefix.c b/test/core/bad_client/tests/connection_prefix.c
index 90d37a3..66ff8c2 100644
--- a/test/core/bad_client/tests/connection_prefix.c
+++ b/test/core/bad_client/tests/connection_prefix.c
@@ -34,7 +34,8 @@
 #include "test/core/bad_client/bad_client.h"
 #include "src/core/surface/server.h"
 
-static void verifier(grpc_server *server, grpc_completion_queue *cq) {
+static void verifier(grpc_server *server, grpc_completion_queue *cq,
+                     void *registered_method) {
   while (grpc_server_has_open_connections(server)) {
     GPR_ASSERT(grpc_completion_queue_next(cq,
                                           GRPC_TIMEOUT_MILLIS_TO_DEADLINE(20),
diff --git a/test/core/bad_client/tests/headers.c b/test/core/bad_client/tests/headers.c
index c404fd2..2186a4f 100644
--- a/test/core/bad_client/tests/headers.c
+++ b/test/core/bad_client/tests/headers.c
@@ -38,7 +38,8 @@
   "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" \
   "\x00\x00\x00\x04\x00\x00\x00\x00\x00"
 
-static void verifier(grpc_server *server, grpc_completion_queue *cq) {
+static void verifier(grpc_server *server, grpc_completion_queue *cq,
+                     void *registered_method) {
   while (grpc_server_has_open_connections(server)) {
     GPR_ASSERT(grpc_completion_queue_next(cq,
                                           GRPC_TIMEOUT_MILLIS_TO_DEADLINE(20),
diff --git a/test/core/bad_client/tests/initial_settings_frame.c b/test/core/bad_client/tests/initial_settings_frame.c
index cd8771e..fb6149c 100644
--- a/test/core/bad_client/tests/initial_settings_frame.c
+++ b/test/core/bad_client/tests/initial_settings_frame.c
@@ -37,7 +37,8 @@
 #define PFX_STR "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
 #define ONE_SETTING_HDR "\x00\x00\x06\x04\x00\x00\x00\x00\x00"
 
-static void verifier(grpc_server *server, grpc_completion_queue *cq) {
+static void verifier(grpc_server *server, grpc_completion_queue *cq,
+                     void *registered_method) {
   while (grpc_server_has_open_connections(server)) {
     GPR_ASSERT(grpc_completion_queue_next(cq,
                                           GRPC_TIMEOUT_MILLIS_TO_DEADLINE(20),
diff --git a/test/core/bad_client/tests/server_registered_method.c b/test/core/bad_client/tests/server_registered_method.c
new file mode 100644
index 0000000..d2876ff
--- /dev/null
+++ b/test/core/bad_client/tests/server_registered_method.c
@@ -0,0 +1,155 @@
+/*
+ *
+ * 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 "test/core/bad_client/bad_client.h"
+
+#include <string.h>
+
+#include "test/core/end2end/cq_verifier.h"
+#include "src/core/surface/server.h"
+
+#define PFX_STR                                               \
+  "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"                          \
+  "\x00\x00\x00\x04\x00\x00\x00\x00\x00" /* settings frame */ \
+  "\x00\x00\xd0\x01\x04\x00\x00\x00\x01"                      \
+  "\x10\x05:path\x0f/registered/bar"                          \
+  "\x10\x07:scheme\x04http"                                   \
+  "\x10\x07:method\x04POST"                                   \
+  "\x10\x0a:authority\x09localhost"                           \
+  "\x10\x0c"                                                  \
+  "content-type\x10"                                          \
+  "application/grpc"                                          \
+  "\x10\x14grpc-accept-encoding\x15identity,deflate,gzip"     \
+  "\x10\x02te\x08trailers"                                    \
+  "\x10\x0auser-agent\"bad-client grpc-c/0.12.0.0 (linux)"
+
+static void *tag(gpr_intptr t) { return (void *)t; }
+
+static void verifier_succeeds(grpc_server *server, grpc_completion_queue *cq,
+                              void *registered_method) {
+  grpc_call_error error;
+  grpc_call *s;
+  cq_verifier *cqv = cq_verifier_create(cq);
+  grpc_metadata_array request_metadata_recv;
+  gpr_timespec deadline;
+  grpc_byte_buffer *payload = NULL;
+
+  grpc_metadata_array_init(&request_metadata_recv);
+
+  error = grpc_server_request_registered_call(server, registered_method, &s,
+                                              &deadline, &request_metadata_recv,
+                                              &payload, cq, cq, tag(101));
+  GPR_ASSERT(GRPC_CALL_OK == error);
+  cq_expect_completion(cqv, tag(101), 1);
+  cq_verify(cqv);
+
+  GPR_ASSERT(payload != NULL);
+
+  grpc_metadata_array_destroy(&request_metadata_recv);
+  grpc_call_destroy(s);
+  grpc_byte_buffer_destroy(payload);
+  cq_verifier_destroy(cqv);
+}
+
+static void verifier_fails(grpc_server *server, grpc_completion_queue *cq,
+                           void *registered_method) {
+  grpc_call_error error;
+  grpc_call *s;
+  cq_verifier *cqv = cq_verifier_create(cq);
+  grpc_metadata_array request_metadata_recv;
+  gpr_timespec deadline;
+  grpc_byte_buffer *payload = NULL;
+
+  grpc_metadata_array_init(&request_metadata_recv);
+
+  error = grpc_server_request_registered_call(server, registered_method, &s,
+                                              &deadline, &request_metadata_recv,
+                                              &payload, cq, cq, tag(101));
+  GPR_ASSERT(GRPC_CALL_OK == error);
+  cq_expect_completion(cqv, tag(101), 1);
+  cq_verify(cqv);
+
+  GPR_ASSERT(payload == NULL);
+
+  grpc_metadata_array_destroy(&request_metadata_recv);
+  grpc_call_destroy(s);
+  cq_verifier_destroy(cqv);
+}
+
+int main(int argc, char **argv) {
+  grpc_test_init(argc, argv);
+
+  /* body generated with
+   * tools/codegen/core/gen_server_registered_method_bad_client_test_body.py */
+  GRPC_RUN_BAD_CLIENT_TEST(verifier_fails,
+                           PFX_STR "\x00\x00\x00\x00\x00\x00\x00\x00\x01",
+                           GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(verifier_fails,
+                           PFX_STR "\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00",
+                           GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(verifier_fails, PFX_STR
+                           "\x00\x00\x02\x00\x00\x00\x00\x00\x01\x00\x00",
+                           GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(verifier_fails, PFX_STR
+                           "\x00\x00\x03\x00\x00\x00\x00\x00\x01\x00\x00\x00",
+                           GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(
+      verifier_fails,
+      PFX_STR "\x00\x00\x04\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00",
+      GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(
+      verifier_succeeds,
+      PFX_STR "\x00\x00\x05\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00", 0);
+  GRPC_RUN_BAD_CLIENT_TEST(
+      verifier_fails,
+      PFX_STR "\x00\x00\x05\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01",
+      GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(
+      verifier_succeeds,
+      PFX_STR "\x00\x00\x06\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x01\x00",
+      0);
+  GRPC_RUN_BAD_CLIENT_TEST(
+      verifier_fails,
+      PFX_STR "\x00\x00\x05\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x02",
+      GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(
+      verifier_fails,
+      PFX_STR "\x00\x00\x06\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x02\x00",
+      GRPC_BAD_CLIENT_DISCONNECT);
+  GRPC_RUN_BAD_CLIENT_TEST(
+      verifier_succeeds, PFX_STR
+      "\x00\x00\x07\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x02\x00\x00",
+      0);
+
+  return 0;
+}
diff --git a/test/core/bad_client/tests/server_registered_method.headers b/test/core/bad_client/tests/server_registered_method.headers
new file mode 100644
index 0000000..06ee73c
--- /dev/null
+++ b/test/core/bad_client/tests/server_registered_method.headers
@@ -0,0 +1,12 @@
+# headers used in simple_request.c
+# use tools/codegen/core/gen_header_frame.py to generate the binary strings
+# contained in the source code
+:path: /registered/bar
+:scheme: http
+:method: POST
+:authority: localhost
+content-type: application/grpc
+grpc-accept-encoding: identity,deflate,gzip
+te: trailers
+user-agent: bad-client grpc-c/0.12.0.0 (linux)
+
diff --git a/test/core/bad_client/tests/simple_request.c b/test/core/bad_client/tests/simple_request.c
index 125a9fa..4df586e 100644
--- a/test/core/bad_client/tests/simple_request.c
+++ b/test/core/bad_client/tests/simple_request.c
@@ -79,7 +79,8 @@
 
 static void *tag(gpr_intptr t) { return (void *)t; }
 
-static void verifier(grpc_server *server, grpc_completion_queue *cq) {
+static void verifier(grpc_server *server, grpc_completion_queue *cq,
+                     void *registered_method) {
   grpc_call_error error;
   grpc_call *s;
   grpc_call_details call_details;
@@ -104,7 +105,8 @@
   cq_verifier_destroy(cqv);
 }
 
-static void failure_verifier(grpc_server *server, grpc_completion_queue *cq) {
+static void failure_verifier(grpc_server *server, grpc_completion_queue *cq,
+                             void *registered_method) {
   while (grpc_server_has_open_connections(server)) {
     GPR_ASSERT(grpc_completion_queue_next(cq,
                                           GRPC_TIMEOUT_MILLIS_TO_DEADLINE(20),
diff --git a/test/core/bad_client/tests/unknown_frame.c b/test/core/bad_client/tests/unknown_frame.c
index 3f8658b..2ef340e 100644
--- a/test/core/bad_client/tests/unknown_frame.c
+++ b/test/core/bad_client/tests/unknown_frame.c
@@ -38,7 +38,8 @@
   "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" \
   "\x00\x00\x00\x04\x00\x00\x00\x00\x00"
 
-static void verifier(grpc_server *server, grpc_completion_queue *cq) {
+static void verifier(grpc_server *server, grpc_completion_queue *cq,
+                     void *registered_method) {
   while (grpc_server_has_open_connections(server)) {
     GPR_ASSERT(grpc_completion_queue_next(cq,
                                           GRPC_TIMEOUT_MILLIS_TO_DEADLINE(20),
diff --git a/test/core/surface/completion_queue_test.c b/test/core/surface/completion_queue_test.c
index 5cf6bfe..8a24d82 100644
--- a/test/core/surface/completion_queue_test.c
+++ b/test/core/surface/completion_queue_test.c
@@ -89,7 +89,7 @@
 
   cc = grpc_completion_queue_create(NULL);
 
-  grpc_cq_begin_op(cc);
+  grpc_cq_begin_op(cc, tag);
   grpc_cq_end_op(&exec_ctx, cc, tag, 1, do_nothing_end_completion, NULL,
                  &completion);
 
@@ -148,7 +148,7 @@
   cc = grpc_completion_queue_create(NULL);
 
   for (i = 0; i < GPR_ARRAY_SIZE(tags); i++) {
-    grpc_cq_begin_op(cc);
+    grpc_cq_begin_op(cc, tags[i]);
     grpc_cq_end_op(&exec_ctx, cc, tags[i], 1, do_nothing_end_completion, NULL,
                    &completions[i]);
   }
@@ -160,7 +160,7 @@
   }
 
   for (i = 0; i < GPR_ARRAY_SIZE(tags); i++) {
-    grpc_cq_begin_op(cc);
+    grpc_cq_begin_op(cc, tags[i]);
     grpc_cq_end_op(&exec_ctx, cc, tags[i], 1, do_nothing_end_completion, NULL,
                    &completions[i]);
   }
@@ -226,14 +226,14 @@
   }
 
   /* wait until all other threads are plucking */
-  gpr_sleep_until(GRPC_TIMEOUT_MILLIS_TO_DEADLINE(100));
+  gpr_sleep_until(GRPC_TIMEOUT_MILLIS_TO_DEADLINE(1000));
 
   ev = grpc_completion_queue_pluck(cc, create_test_tag(),
                                    gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
   GPR_ASSERT(ev.type == GRPC_QUEUE_TIMEOUT);
 
   for (i = 0; i < GPR_ARRAY_SIZE(tags); i++) {
-    grpc_cq_begin_op(cc);
+    grpc_cq_begin_op(cc, tags[i]);
     grpc_cq_end_op(&exec_ctx, cc, tags[i], 1, do_nothing_end_completion, NULL,
                    &completions[i]);
   }
@@ -279,7 +279,7 @@
 
   gpr_log(GPR_INFO, "producer %d phase 1", opt->id);
   for (i = 0; i < TEST_THREAD_EVENTS; i++) {
-    grpc_cq_begin_op(opt->cc);
+    grpc_cq_begin_op(opt->cc, (void *)(gpr_intptr)1);
   }
 
   gpr_log(GPR_INFO, "producer %d phase 1 done", opt->id);
diff --git a/test/core/util/test_config.c b/test/core/util/test_config.c
index 84b376a..14bfc95 100644
--- a/test/core/util/test_config.c
+++ b/test/core/util/test_config.c
@@ -127,6 +127,8 @@
     backtrace_symbols_fd(addrlist, addrlen, STDERR_FILENO);
   }
 
+  /* try to get a core dump for SIGTERM */
+  if (signum == SIGTERM) signum = SIGQUIT;
   raise(signum);
 }
 
@@ -145,6 +147,8 @@
   GPR_ASSERT(sigaction(SIGABRT, &sa, NULL) == 0);
   GPR_ASSERT(sigaction(SIGBUS, &sa, NULL) == 0);
   GPR_ASSERT(sigaction(SIGSEGV, &sa, NULL) == 0);
+  GPR_ASSERT(sigaction(SIGTERM, &sa, NULL) == 0);
+  GPR_ASSERT(sigaction(SIGQUIT, &sa, NULL) == 0);
 }
 #else
 static void install_crash_handler() {}
diff --git a/test/cpp/interop/interop_client.cc b/test/cpp/interop/interop_client.cc
index 6340007..05ef0c8 100644
--- a/test/cpp/interop/interop_client.cc
+++ b/test/cpp/interop/interop_client.cc
@@ -80,6 +80,24 @@
       GPR_ASSERT(false);
   }
 }
+
+void NoopChecks(const InteropClientContextInspector& inspector,
+                const SimpleRequest* request, const SimpleResponse* response) {}
+
+void CompressionChecks(const InteropClientContextInspector& inspector,
+                       const SimpleRequest* request,
+                       const SimpleResponse* response) {
+  GPR_ASSERT(request->response_compression() ==
+             GetInteropCompressionTypeFromCompressionAlgorithm(
+                 inspector.GetCallCompressionAlgorithm()));
+  if (request->response_compression() == NONE) {
+    GPR_ASSERT(!(inspector.GetMessageFlags() & GRPC_WRITE_INTERNAL_COMPRESS));
+  } else if (request->response_type() == PayloadType::COMPRESSABLE) {
+    // requested compression and compressable response => results should always
+    // be compressed.
+    GPR_ASSERT(inspector.GetMessageFlags() & GRPC_WRITE_INTERNAL_COMPRESS);
+  }
+}
 }  // namespace
 
 InteropClient::ServiceStub::ServiceStub(std::shared_ptr<Channel> channel,
@@ -145,9 +163,14 @@
   gpr_log(GPR_INFO, "Empty rpc done.");
 }
 
-// Shared code to set large payload, make rpc and check response payload.
 void InteropClient::PerformLargeUnary(SimpleRequest* request,
                                       SimpleResponse* response) {
+  PerformLargeUnary(request, response, NoopChecks);
+}
+
+void InteropClient::PerformLargeUnary(SimpleRequest* request,
+                                      SimpleResponse* response,
+                                      CheckerFn custom_checks_fn) {
   ClientContext context;
   InteropClientContextInspector inspector(context);
   // If the request doesn't already specify the response type, default to
@@ -157,21 +180,10 @@
   request->mutable_payload()->set_body(payload.c_str(), kLargeRequestSize);
 
   Status s = serviceStub_.Get()->UnaryCall(&context, *request, response);
-
-  // Compression related checks.
-  GPR_ASSERT(request->response_compression() ==
-             GetInteropCompressionTypeFromCompressionAlgorithm(
-                 inspector.GetCallCompressionAlgorithm()));
-  if (request->response_compression() == NONE) {
-    GPR_ASSERT(!(inspector.GetMessageFlags() & GRPC_WRITE_INTERNAL_COMPRESS));
-  } else if (request->response_type() == PayloadType::COMPRESSABLE) {
-    // requested compression and compressable response => results should always
-    // be compressed.
-    GPR_ASSERT(inspector.GetMessageFlags() & GRPC_WRITE_INTERNAL_COMPRESS);
-  }
-
   AssertOkOrPrintErrorStatus(s);
 
+  custom_checks_fn(inspector, request, response);
+
   // Payload related checks.
   if (request->response_type() != PayloadType::RANDOM) {
     GPR_ASSERT(response->payload().type() == request->response_type());
@@ -293,7 +305,7 @@
       SimpleResponse response;
       request.set_response_type(payload_types[i]);
       request.set_response_compression(compression_types[j]);
-      PerformLargeUnary(&request, &response);
+      PerformLargeUnary(&request, &response, CompressionChecks);
       gpr_log(GPR_INFO, "Large compressed unary done %s.", log_suffix);
       gpr_free(log_suffix);
     }
diff --git a/test/cpp/interop/interop_client.h b/test/cpp/interop/interop_client.h
index 1bfb49d..97a6fd7 100644
--- a/test/cpp/interop/interop_client.h
+++ b/test/cpp/interop/interop_client.h
@@ -44,6 +44,11 @@
 namespace grpc {
 namespace testing {
 
+// Function pointer for custom checks.
+using CheckerFn =
+    std::function<void(const InteropClientContextInspector&,
+                       const SimpleRequest*, const SimpleResponse*)>;
+
 class InteropClient {
  public:
   explicit InteropClient(std::shared_ptr<Channel> channel);
@@ -100,6 +105,10 @@
   };
 
   void PerformLargeUnary(SimpleRequest* request, SimpleResponse* response);
+
+  /// Run \a custom_check_fn as an additional check.
+  void PerformLargeUnary(SimpleRequest* request, SimpleResponse* response,
+                         CheckerFn custom_checks_fn);
   void AssertOkOrPrintErrorStatus(const Status& s);
   ServiceStub serviceStub_;
 };
diff --git a/tools/codegen/core/gen_server_registered_method_bad_client_test_body.py b/tools/codegen/core/gen_server_registered_method_bad_client_test_body.py
new file mode 100755
index 0000000..76c9876
--- /dev/null
+++ b/tools/codegen/core/gen_server_registered_method_bad_client_test_body.py
@@ -0,0 +1,76 @@
+#!/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.
+
+def esc_c(line):
+  out = "\""
+  last_was_hex = False
+  for c in line:
+    if 32 <= c < 127:
+      if c in hex_bytes and last_was_hex:
+        out += "\"\""
+      if c != ord('"'):
+        out += chr(c)
+      else:
+        out += "\\\""
+      last_was_hex = False
+    else:
+      out += "\\x%02x" % c
+      last_was_hex = True
+  return out + "\""
+
+done = set()
+
+for message_length in range(0, 3):
+  for send_message_length in range(0, message_length + 1):
+    payload = [
+      0,
+      (message_length >> 24) & 0xff,
+      (message_length >> 16) & 0xff,
+      (message_length >> 8) & 0xff,
+      (message_length) & 0xff
+    ] + send_message_length * [0]
+    for frame_length in range(0, len(payload) + 1):
+      is_end = frame_length == len(payload) and send_message_length == message_length
+      frame = [
+        (frame_length >> 16) & 0xff,
+        (frame_length >> 8) & 0xff,
+        (frame_length) & 0xff,
+        0,
+        1 if is_end else 0,
+        0, 0, 0, 1
+      ] + payload[0:frame_length]
+      text = esc_c(frame)
+      if text not in done:
+        print 'GRPC_RUN_BAD_CLIENT_TEST(verifier_%s, PFX_STR %s, %s);' % (
+            'succeeds' if is_end else 'fails', 
+            text, 
+            '0' if is_end else 'GRPC_BAD_CLIENT_DISCONNECT')
+        done.add(text)
diff --git a/tools/jenkins/grpc_interop_php/Dockerfile b/tools/jenkins/grpc_interop_php/Dockerfile
index 0780894..cf3e791 100644
--- a/tools/jenkins/grpc_interop_php/Dockerfile
+++ b/tools/jenkins/grpc_interop_php/Dockerfile
@@ -100,9 +100,10 @@
 RUN curl -sS https://getcomposer.org/installer | php
 RUN mv composer.phar /usr/local/bin/composer
 
-# attempt to force a rebuild of the docker image after this point because
-# Protobuf-PHP codegen has been updated
-RUN echo 1
+# As an attempt to work around #4212, try to prefetch Protobuf-PHP dependency
+# into composer cache to prevent "composer install" from cloning on each build.
+RUN git clone --mirror https://github.com/stanley-cheung/Protobuf-PHP.git \
+  /root/.composer/cache/vcs/git-github.com-stanley-cheung-Protobuf-PHP.git/
 
 # Download the patched PHP protobuf so that PHP gRPC clients can be generated
 # from proto3 schemas.
@@ -113,10 +114,5 @@
   && rvm all do rake pear:package version=1.0 \
   && pear install Protobuf-1.0.tgz"
 
-# As an attempt to work around #4212, try to prefetch Protobuf-PHP dependency
-# into composer cache to prevent "composer install" from cloning on each build.
-RUN git clone --mirror https://github.com/stanley-cheung/Protobuf-PHP.git \
-  /root/.composer/cache/vcs/git-github.com-stanley-cheung-Protobuf-PHP.git/
-
 # Define the default command.
 CMD ["bash"]
diff --git a/tools/jenkins/run_performance.sh b/tools/jenkins/run_performance.sh
index a60b1bb..d7e73d4 100755
--- a/tools/jenkins/run_performance.sh
+++ b/tools/jenkins/run_performance.sh
@@ -34,6 +34,10 @@
 # Enter the gRPC repo root
 cd $(dirname $0)/../..
 
+[[ $* =~ '--latency_profile' ]] \
+	&& tools/profiling/latency_profile/run_latency_profile.sh \
+	|| true
+
 config=opt
 
 make CONFIG=$config qps_worker qps_driver -j8
@@ -45,7 +49,7 @@
 
 export QPS_WORKERS="localhost:10000,localhost:10010"
 
-bins/$config/qps_driver $*
+bins/$config/qps_driver
 
 kill -2 $PID1 $PID2
 wait
diff --git a/tools/profiling/latency_profile/run_latency_profile.sh b/tools/profiling/latency_profile/run_latency_profile.sh
index 41686be..64c3e58 100755
--- a/tools/profiling/latency_profile/run_latency_profile.sh
+++ b/tools/profiling/latency_profile/run_latency_profile.sh
@@ -11,6 +11,17 @@
 
 mkdir -p reports
 
+# 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
+
+# start processes, interleaving report index generation
 echo '<html><head></head><body>' > reports/index.html
 for bin in $BINS
 do
@@ -18,12 +29,27 @@
   mv latency_trace.txt $bin.trace
   echo "<a href='$bin.txt'>$bin</a><br/>" >> reports/index.html
 done
+pids=""
+# generate report pages... this will take some time
+# run them in parallel: they take 1 cpu each
 for bin in $BINS
 do
-  tools/profiling/latency_profile/profile_analyzer.py \
+  $PYTHON tools/profiling/latency_profile/profile_analyzer.py \
     --source=$bin.trace --fmt=simple > reports/$bin.txt &
+  pids+=" $!"
 done
 echo '</body></html>' >> reports/index.html
 
-wait
+# make sure we kill the report generation if something goes wrong
+trap "kill $pids || true" 0
 
+# finally, wait for the background report generation to finish
+for pid in $pids
+do
+	if wait $pid
+	then
+		echo "Finished $pid"
+	else
+		exit 1
+	fi
+done
diff --git a/tools/run_tests/build_csharp.sh b/tools/run_tests/build_csharp.sh
index 2c33351..6737d88 100755
--- a/tools/run_tests/build_csharp.sh
+++ b/tools/run_tests/build_csharp.sh
@@ -30,7 +30,7 @@
 
 set -ex
 
-if [ "$CONFIG" = "dbg" ] || [ "$CONFIG" = "gcov" ]
+if [ "$CONFIG" = "dbg" ]
 then
   MSBUILD_CONFIG="Debug"
 else
diff --git a/tools/run_tests/jobset.py b/tools/run_tests/jobset.py
index fdbddf8..48afbaf 100755
--- a/tools/run_tests/jobset.py
+++ b/tools/run_tests/jobset.py
@@ -273,7 +273,7 @@
           update_cache.finished(self._spec.identity(), self._bin_hash)
     elif self._state == _RUNNING and time.time() - self._start > self._spec.timeout_seconds:
       if self._timeout_retries < self._spec.timeout_retries:
-        message('TIMEOUT_FLAKE', self._spec.shortname, stdout, do_newline=True)
+        message('TIMEOUT_FLAKE', '%s [pid=%d]' % (self._spec.shortname, self._process.pid), stdout, do_newline=True)
         self._timeout_retries += 1
         self.result.num_failures += 1
         self.result.retries = self._timeout_retries + self._retries
@@ -282,7 +282,7 @@
         self._process.terminate()
         self.start()
       else:
-        message('TIMEOUT', self._spec.shortname, stdout, do_newline=True)
+        message('TIMEOUT', '%s [pid=%d]' % (self._spec.shortname, self._process.pid), stdout, do_newline=True)
         self.kill()
         self.result.state = 'TIMEOUT'
         self.result.num_failures += 1
diff --git a/tools/run_tests/run_csharp.bat b/tools/run_tests/run_csharp.bat
index 0aa32ea..82eb585 100644
--- a/tools/run_tests/run_csharp.bat
+++ b/tools/run_tests/run_csharp.bat
@@ -6,16 +6,11 @@
 cd /d %~dp0\..\..\src\csharp
 
 if not "%CONFIG%" == "gcov" (
-  @rem Run tests for assembly passed as 1st arg.
-
-  @rem set UUID variable to a random GUID, we will use it to put TestResults.xml to a dedicated directory, so that parallel test runs don't collide
-  for /F %%i in ('powershell -Command "[guid]::NewGuid().ToString()"') do (set UUID=%%i)
-
-  packages\NUnit.Runners.2.6.4\tools\nunit-console-x86.exe /domain:None -labels "%1/bin/Debug/%1.dll" -work test-results/%UUID% || goto :error
+  packages\NUnit.Runners.2.6.4\tools\nunit-console-x86.exe %* || goto :error
 ) else (
   @rem Run all tests with code coverage
 
-  packages\OpenCover.4.6.166\tools\OpenCover.Console.exe -target:"packages\NUnit.Runners.2.6.4\tools\nunit-console-x86.exe" -targetdir:"." -targetargs:"/domain:None -labels Grpc.Core.Tests/bin/Debug/Grpc.Core.Tests.dll Grpc.IntegrationTesting/bin/Debug/Grpc.IntegrationTesting.dll Grpc.Examples.Tests/bin/Debug/Grpc.Examples.Tests.dll Grpc.HealthCheck.Tests/bin/Debug/Grpc.HealthCheck.Tests.dll" -filter:"+[Grpc.Core]*"  -register:user -output:coverage_results.xml || goto :error
+  packages\OpenCover.4.6.166\tools\OpenCover.Console.exe -target:"packages\NUnit.Runners.2.6.4\tools\nunit-console-x86.exe" -targetdir:"." -targetargs:"%*" -filter:"+[Grpc.Core]*"  -register:user -output:coverage_results.xml || goto :error
 
   packages\ReportGenerator.2.3.2.0\tools\ReportGenerator.exe -reports:"coverage_results.xml" -targetdir:"..\..\reports\csharp_coverage" -reporttypes:"Html;TextSummary" || goto :error
 
diff --git a/tools/run_tests/run_csharp.sh b/tools/run_tests/run_csharp.sh
index 37e86fe..744df07 100755
--- a/tools/run_tests/run_csharp.sh
+++ b/tools/run_tests/run_csharp.sh
@@ -31,38 +31,25 @@
 set -ex
 
 CONFIG=${CONFIG:-opt}
-
 NUNIT_CONSOLE="mono packages/NUnit.Runners.2.6.4/tools/nunit-console.exe"
 
-if [ "$CONFIG" = "dbg" ] || [ "$CONFIG" = "gcov" ]
-then
-  MSBUILD_CONFIG="Debug"
-else
-  MSBUILD_CONFIG="Release"
-fi
-
 # change to gRPC repo root
 cd $(dirname $0)/../..
 
-root=`pwd`
-export LD_LIBRARY_PATH=$root/libs/$CONFIG
+# path needs to be absolute
+export LD_LIBRARY_PATH=$(pwd)/libs/$CONFIG
+
+(cd src/csharp; $NUNIT_CONSOLE $@)
 
 if [ "$CONFIG" = "gcov" ]
 then
-  (cd src/csharp; $NUNIT_CONSOLE -labels \
-      "Grpc.Core.Tests/bin/$MSBUILD_CONFIG/Grpc.Core.Tests.dll" \
-      "Grpc.Examples.Tests/bin/$MSBUILD_CONFIG/Grpc.Examples.Tests.dll" \
-      "Grpc.HealthCheck.Tests/bin/$MSBUILD_CONFIG/Grpc.HealthCheck.Tests.dll" \
-      "Grpc.IntegrationTesting/bin/$MSBUILD_CONFIG/Grpc.IntegrationTesting.dll")
-
+  # Generate the csharp extension coverage report
   gcov objs/gcov/src/csharp/ext/*.o
   lcov --base-directory . --directory . -c -o coverage.info
   lcov -e coverage.info '**/src/csharp/ext/*' -o coverage.info
   genhtml -o reports/csharp_ext_coverage --num-spaces 2 \
     -t 'gRPC C# native extension test coverage' coverage.info \
     --rc genhtml_hi_limit=95 --rc genhtml_med_limit=80 --no-prefix
-else
-  (cd src/csharp; $NUNIT_CONSOLE -labels "$1/bin/$MSBUILD_CONFIG/$1.dll")
 fi
 
 
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 006f4bc..3803e8c 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -47,6 +47,7 @@
 import traceback
 import time
 import urllib2
+import uuid
 
 import jobset
 import report_utils
@@ -336,26 +337,42 @@
     self.platform = platform_string()
 
   def test_specs(self, config, args):
-    assemblies = ['Grpc.Core.Tests',
-                  'Grpc.Examples.Tests',
-                  'Grpc.HealthCheck.Tests',
-                  'Grpc.IntegrationTesting']
+    with open('src/csharp/tests.json') as f:
+      tests_json = json.load(f)
+    assemblies = tests_json['assemblies']
+    tests = tests_json['tests']
+
+    msbuild_config = _WINDOWS_CONFIG[config.build_config]
+    assembly_files = ['%s/bin/%s/%s.dll' % (a, msbuild_config, a)
+                      for a in assemblies]
+
+    extra_args = ['-labels'] + assembly_files
+
     if self.platform == 'windows':
-      cmd = 'tools\\run_tests\\run_csharp.bat'
+      script_name = 'tools\\run_tests\\run_csharp.bat'
+      extra_args += ['-domain=None']
     else:
-      cmd = 'tools/run_tests/run_csharp.sh'
+      script_name = 'tools/run_tests/run_csharp.sh'
 
     if config.build_config == 'gcov':
       # On Windows, we only collect C# code coverage.
       # On Linux, we only collect coverage for native extension.
       # For code coverage all tests need to run as one suite.
-      return [config.job_spec([cmd], None,
-              environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
+      return [config.job_spec([script_name] + extra_args, None,
+                              shortname='csharp.coverage',
+                              environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
     else:
-      return [config.job_spec([cmd, assembly],
-              None, shortname=assembly,
-              environ=_FORCE_ENVIRON_FOR_WRAPPERS)
-              for assembly in assemblies]
+      specs = []
+      for test in tests:
+        cmdline = [script_name, '-run=%s' % test] + extra_args
+        if self.platform == 'windows':
+          # use different output directory for each test to prevent
+          # TestResult.xml clash between parallel test runs.
+          cmdline += ['-work=test-result/%s' % uuid.uuid4()]
+        specs.append(config.job_spec(cmdline, None,
+                                     shortname='csharp.%s' % test,
+                                     environ=_FORCE_ENVIRON_FOR_WRAPPERS))
+      return specs
 
   def pre_build_steps(self):
     if self.platform == 'windows':
@@ -509,6 +526,7 @@
 _WINDOWS_CONFIG = {
     'dbg': 'Debug',
     'opt': 'Release',
+    'gcov': 'Release',
     }
 
 
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index 2ea8715..5cd1b2c 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -17257,6 +17257,21 @@
     ], 
     "headers": [], 
     "language": "c", 
+    "name": "server_registered_method_bad_client_test", 
+    "src": [
+      "test/core/bad_client/tests/server_registered_method.c"
+    ]
+  }, 
+  {
+    "deps": [
+      "bad_client_test", 
+      "gpr", 
+      "gpr_test_util", 
+      "grpc_test_util_unsecure", 
+      "grpc_unsecure"
+    ], 
+    "headers": [], 
+    "language": "c", 
     "name": "simple_request_bad_client_test", 
     "src": [
       "test/core/bad_client/tests/simple_request.c"
diff --git a/tools/run_tests/tests.json b/tools/run_tests/tests.json
index 5f75acc..1107899 100644
--- a/tools/run_tests/tests.json
+++ b/tools/run_tests/tests.json
@@ -18402,6 +18402,24 @@
     "exclude_configs": [], 
     "flaky": false, 
     "language": "c", 
+    "name": "server_registered_method_bad_client_test", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ]
+  }, 
+  {
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix", 
+      "windows"
+    ], 
+    "exclude_configs": [], 
+    "flaky": false, 
+    "language": "c", 
     "name": "simple_request_bad_client_test", 
     "platforms": [
       "linux", 
diff --git a/vsprojects/buildtests_c.sln b/vsprojects/buildtests_c.sln
index 5e5302a..7225f14 100644
--- a/vsprojects/buildtests_c.sln
+++ b/vsprojects/buildtests_c.sln
@@ -10903,6 +10903,18 @@
 		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
 	EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "server_registered_method_bad_client_test", "vcxproj\test\server_registered_method_bad_client_test\server_registered_method_bad_client_test.vcxproj", "{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}"
+	ProjectSection(myProperties) = preProject
+        	lib = "False"
+	EndProjectSection
+	ProjectSection(ProjectDependencies) = postProject
+		{BA67B418-B699-E41A-9CC4-0279C49481A5} = {BA67B418-B699-E41A-9CC4-0279C49481A5}
+		{0A7E7F92-FDEA-40F1-A9EC-3BA484F98BBF} = {0A7E7F92-FDEA-40F1-A9EC-3BA484F98BBF}
+		{46CEDFFF-9692-456A-AA24-38B5D6BCF4C5} = {46CEDFFF-9692-456A-AA24-38B5D6BCF4C5}
+		{EAB0A629-17A9-44DB-B5FF-E91A721FE037} = {EAB0A629-17A9-44DB-B5FF-E91A721FE037}
+		{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}
+	EndProjectSection
+EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "simple_request_bad_client_test", "vcxproj\test\simple_request_bad_client_test\simple_request_bad_client_test.vcxproj", "{63422647-93FA-46BB-4827-95473D9D503C}"
 	ProjectSection(myProperties) = preProject
         	lib = "False"
@@ -24347,6 +24359,22 @@
 		{6756895E-05BF-8CC7-58F2-868DF0C0300C}.Release-DLL|Win32.Build.0 = Release|Win32
 		{6756895E-05BF-8CC7-58F2-868DF0C0300C}.Release-DLL|x64.ActiveCfg = Release|x64
 		{6756895E-05BF-8CC7-58F2-868DF0C0300C}.Release-DLL|x64.Build.0 = Release|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug|Win32.ActiveCfg = Debug|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug|x64.ActiveCfg = Debug|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release|Win32.ActiveCfg = Release|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release|x64.ActiveCfg = Release|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug|Win32.Build.0 = Debug|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug|x64.Build.0 = Debug|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release|Win32.Build.0 = Release|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release|x64.Build.0 = Release|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug-DLL|Win32.ActiveCfg = Debug|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug-DLL|Win32.Build.0 = Debug|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug-DLL|x64.ActiveCfg = Debug|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Debug-DLL|x64.Build.0 = Debug|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release-DLL|Win32.ActiveCfg = Release|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release-DLL|Win32.Build.0 = Release|Win32
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release-DLL|x64.ActiveCfg = Release|x64
+		{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}.Release-DLL|x64.Build.0 = Release|x64
 		{63422647-93FA-46BB-4827-95473D9D503C}.Debug|Win32.ActiveCfg = Debug|Win32
 		{63422647-93FA-46BB-4827-95473D9D503C}.Debug|x64.ActiveCfg = Debug|x64
 		{63422647-93FA-46BB-4827-95473D9D503C}.Release|Win32.ActiveCfg = Release|Win32
diff --git a/vsprojects/vcxproj/test/server_registered_method_bad_client_test/server_registered_method_bad_client_test.vcxproj b/vsprojects/vcxproj/test/server_registered_method_bad_client_test/server_registered_method_bad_client_test.vcxproj
new file mode 100644
index 0000000..6f8b7eb
--- /dev/null
+++ b/vsprojects/vcxproj/test/server_registered_method_bad_client_test/server_registered_method_bad_client_test.vcxproj
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props" Condition="Exists('..\..\..\..\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>{B4E7CD82-988A-BD3A-29F8-8590D3A8BC28}</ProjectGuid>
+  </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="..\..\..\..\vsprojects\global.props" />
+    <Import Project="..\..\..\..\vsprojects\openssl.props" />
+    <Import Project="..\..\..\..\vsprojects\winsock.props" />
+    <Import Project="..\..\..\..\vsprojects\zlib.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+    <TargetName>server_registered_method_bad_client_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Debug</Configuration-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_openssl>Debug</Configuration-grpc_dependencies_openssl>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <TargetName>server_registered_method_bad_client_test</TargetName>
+    <Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_zlib>Debug</Configuration-grpc_dependencies_zlib>
+    <Configuration-grpc_dependencies_openssl>Debug</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>
+    </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>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation>
+      <GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+    </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>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat>
+    </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="..\..\..\..\test\core\bad_client\tests\server_registered_method.c">
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\..\vsprojects\vcxproj\test\bad_client_test\bad_client_test.vcxproj">
+      <Project>{BA67B418-B699-E41A-9CC4-0279C49481A5}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\..\..\..\vsprojects\vcxproj\.\grpc_test_util_unsecure\grpc_test_util_unsecure.vcxproj">
+      <Project>{0A7E7F92-FDEA-40F1-A9EC-3BA484F98BBF}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\..\..\..\vsprojects\vcxproj\.\grpc_unsecure\grpc_unsecure.vcxproj">
+      <Project>{46CEDFFF-9692-456A-AA24-38B5D6BCF4C5}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\..\..\..\vsprojects\vcxproj\.\gpr_test_util\gpr_test_util.vcxproj">
+      <Project>{EAB0A629-17A9-44DB-B5FF-E91A721FE037}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\..\..\..\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="..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets" Condition="Exists('..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets" Condition="Exists('..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" />
+  <Import Project="..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets" Condition="Exists('..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" />
+  <Import Project="..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets" Condition="Exists('..\..\..\..\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('..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" />
+    <Error Condition="!Exists('..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" />
+    <Error Condition="!Exists('..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" />
+    <Error Condition="!Exists('..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" />
+    <Error Condition="!Exists('..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" />
+  </Target>
+</Project>
+
diff --git a/vsprojects/vcxproj/test/server_registered_method_bad_client_test/server_registered_method_bad_client_test.vcxproj.filters b/vsprojects/vcxproj/test/server_registered_method_bad_client_test/server_registered_method_bad_client_test.vcxproj.filters
new file mode 100644
index 0000000..c74aa2e
--- /dev/null
+++ b/vsprojects/vcxproj/test/server_registered_method_bad_client_test/server_registered_method_bad_client_test.vcxproj.filters
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ClCompile Include="..\..\..\..\test\core\bad_client\tests\server_registered_method.c">
+      <Filter>test\core\bad_client\tests</Filter>
+    </ClCompile>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Filter Include="test">
+      <UniqueIdentifier>{e170992c-1d26-c352-1b77-0393c8e4de0d}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core">
+      <UniqueIdentifier>{aeb9b005-92ba-fd6b-8ad4-4cb18432e9e9}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core\bad_client">
+      <UniqueIdentifier>{a9ea2e08-63e8-2585-98db-9f2c0e409b60}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="test\core\bad_client\tests">
+      <UniqueIdentifier>{3a014dca-e8b5-bd9e-3ce0-64ced4d31a36}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>
+