diff --git a/examples/tips/client.h b/examples/tips/client.h
index 3f4f1fd836990a328b0f16e3cf38f4d4f86d2f2e..6ae9d506580b247270a5eb9df14b6affe529aa6b 100644
--- a/examples/tips/client.h
+++ b/examples/tips/client.h
@@ -31,6 +31,9 @@
  *
  */
 
+#ifndef __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+#define __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
+
 #include <grpc++/channel_interface.h>
 #include <grpc++/status.h>
 
@@ -52,3 +55,5 @@ class Client {
 }  // namespace tips
 }  // namespace examples
 }  // namespace grpc
+
+#endif  // __GRPCPP_EXAMPLES_TIPS_CLIENT_H_
diff --git a/examples/tips/client_main.cc b/examples/tips/client_main.cc
index 17567b6f17a5f4f3e0db822ac4e8a690ad1860b9..f4a3b09601e86ef24525247dddb6ad8e6f63ebcb 100644
--- a/examples/tips/client_main.cc
+++ b/examples/tips/client_main.cc
@@ -41,30 +41,29 @@
 #include "examples/tips/client.h"
 #include "test/cpp/util/create_test_channel.h"
 
-DEFINE_bool(enable_ssl, true, "Whether to use ssl/tls.");
-DEFINE_bool(use_prod_roots, true, "True to use SSL roots for production GFE");
-DEFINE_int32(server_port, 0, "Server port.");
-DEFINE_string(server_host, "127.0.0.1", "Server host to connect to");
-DEFINE_string(server_host_override, "foo.test.google.com",
-              "Override the server host which is sent in HTTP header");
+DEFINE_int32(server_port, 443, "Server port.");
+DEFINE_string(server_host,
+              "pubsub-staging.googleapis.com", "Server host to connect to");
 
 int main(int argc, char** argv) {
   grpc_init();
   google::ParseCommandLineFlags(&argc, &argv, true);
   gpr_log(GPR_INFO, "Start TIPS client");
 
-  GPR_ASSERT(FLAGS_server_port);
   const int host_port_buf_size = 1024;
   char host_port[host_port_buf_size];
   snprintf(host_port, host_port_buf_size, "%s:%d", FLAGS_server_host.c_str(),
            FLAGS_server_port);
 
   std::shared_ptr<grpc::ChannelInterface> channel(
-      grpc::CreateTestChannel(host_port, FLAGS_server_host_override,
-                              FLAGS_enable_ssl, FLAGS_use_prod_roots));
+      grpc::CreateTestChannel(host_port,
+                              FLAGS_server_host,
+                              true,     // enable SSL
+                              true));   // use prod roots
 
   grpc::examples::tips::Client client(channel);
   grpc::Status s = client.CreateTopic("test");
+  gpr_log(GPR_INFO, "return code %d", s.code());
   GPR_ASSERT(s.IsOk());
 
   channel.reset();
diff --git a/src/core/channel/call_op_string.c b/src/core/channel/call_op_string.c
index 789913901ade92a8d33fe838adb3a39134aed290..d36d51e7896560bbdbb4d22ab7a2b92f4ddf99fa 100644
--- a/src/core/channel/call_op_string.c
+++ b/src/core/channel/call_op_string.c
@@ -41,110 +41,86 @@
 #include <grpc/support/alloc.h>
 #include <grpc/support/useful.h>
 
-#define MAX_APPEND 1024
+static void put_metadata(gpr_strvec *b, grpc_mdelem *md) {
+  gpr_strvec_add(b, gpr_strdup(" key="));
+  gpr_strvec_add(b, gpr_hexdump((char *)GPR_SLICE_START_PTR(md->key->slice),
+                    GPR_SLICE_LENGTH(md->key->slice), GPR_HEXDUMP_PLAINTEXT));
 
-typedef struct {
-  size_t cap;
-  size_t len;
-  char *buffer;
-} buf;
-
-static void bprintf(buf *b, const char *fmt, ...) {
-  va_list arg;
-  if (b->len + MAX_APPEND > b->cap) {
-    b->cap = GPR_MAX(b->len + MAX_APPEND, b->cap * 3 / 2);
-    b->buffer = gpr_realloc(b->buffer, b->cap);
-  }
-  va_start(arg, fmt);
-  b->len += vsprintf(b->buffer + b->len, fmt, arg);
-  va_end(arg);
-}
-
-static void bputs(buf *b, const char *s) {
-  size_t slen = strlen(s);
-  if (b->len + slen + 1 > b->cap) {
-    b->cap = GPR_MAX(b->len + slen + 1, b->cap * 3 / 2);
-    b->buffer = gpr_realloc(b->buffer, b->cap);
-  }
-  strcat(b->buffer, s);
-  b->len += slen;
-}
-
-static void put_metadata(buf *b, grpc_mdelem *md) {
-  char *txt;
-
-  txt = gpr_hexdump((char *)GPR_SLICE_START_PTR(md->key->slice),
-                    GPR_SLICE_LENGTH(md->key->slice), GPR_HEXDUMP_PLAINTEXT);
-  bputs(b, " key=");
-  bputs(b, txt);
-  gpr_free(txt);
-
-  txt = gpr_hexdump((char *)GPR_SLICE_START_PTR(md->value->slice),
-                    GPR_SLICE_LENGTH(md->value->slice), GPR_HEXDUMP_PLAINTEXT);
-  bputs(b, " value=");
-  bputs(b, txt);
-  gpr_free(txt);
+  gpr_strvec_add(b, gpr_strdup(" value="));
+  gpr_strvec_add(b, gpr_hexdump((char *)GPR_SLICE_START_PTR(md->value->slice),
+                    GPR_SLICE_LENGTH(md->value->slice), GPR_HEXDUMP_PLAINTEXT));
 }
 
 char *grpc_call_op_string(grpc_call_op *op) {
-  buf b = {0, 0, 0};
+  char *tmp;
+  char *out;
+
+  gpr_strvec b;
+  gpr_strvec_init(&b);
 
   switch (op->dir) {
     case GRPC_CALL_DOWN:
-      bprintf(&b, ">");
+      gpr_strvec_add(&b, gpr_strdup(">"));
       break;
     case GRPC_CALL_UP:
-      bprintf(&b, "<");
+      gpr_strvec_add(&b, gpr_strdup("<"));
       break;
   }
   switch (op->type) {
     case GRPC_SEND_METADATA:
-      bprintf(&b, "SEND_METADATA");
+      gpr_strvec_add(&b, gpr_strdup("SEND_METADATA"));
       put_metadata(&b, op->data.metadata);
       break;
     case GRPC_SEND_DEADLINE:
-      bprintf(&b, "SEND_DEADLINE %d.%09d", op->data.deadline.tv_sec,
+      gpr_asprintf(&tmp, "SEND_DEADLINE %d.%09d", op->data.deadline.tv_sec,
               op->data.deadline.tv_nsec);
+      gpr_strvec_add(&b, tmp);
       break;
     case GRPC_SEND_START:
-      bprintf(&b, "SEND_START pollset=%p", op->data.start.pollset);
+      gpr_asprintf(&tmp, "SEND_START pollset=%p", op->data.start.pollset);
+      gpr_strvec_add(&b, tmp);
       break;
     case GRPC_SEND_MESSAGE:
-      bprintf(&b, "SEND_MESSAGE");
+      gpr_strvec_add(&b, gpr_strdup("SEND_MESSAGE"));
       break;
     case GRPC_SEND_FINISH:
-      bprintf(&b, "SEND_FINISH");
+      gpr_strvec_add(&b, gpr_strdup("SEND_FINISH"));
       break;
     case GRPC_REQUEST_DATA:
-      bprintf(&b, "REQUEST_DATA");
+      gpr_strvec_add(&b, gpr_strdup("REQUEST_DATA"));
       break;
     case GRPC_RECV_METADATA:
-      bprintf(&b, "RECV_METADATA");
+      gpr_strvec_add(&b, gpr_strdup("RECV_METADATA"));
       put_metadata(&b, op->data.metadata);
       break;
     case GRPC_RECV_DEADLINE:
-      bprintf(&b, "RECV_DEADLINE %d.%09d", op->data.deadline.tv_sec,
+      gpr_asprintf(&tmp, "RECV_DEADLINE %d.%09d", op->data.deadline.tv_sec,
               op->data.deadline.tv_nsec);
+      gpr_strvec_add(&b, tmp);
       break;
     case GRPC_RECV_END_OF_INITIAL_METADATA:
-      bprintf(&b, "RECV_END_OF_INITIAL_METADATA");
+      gpr_strvec_add(&b, gpr_strdup("RECV_END_OF_INITIAL_METADATA"));
       break;
     case GRPC_RECV_MESSAGE:
-      bprintf(&b, "RECV_MESSAGE");
+      gpr_strvec_add(&b, gpr_strdup("RECV_MESSAGE"));
       break;
     case GRPC_RECV_HALF_CLOSE:
-      bprintf(&b, "RECV_HALF_CLOSE");
+      gpr_strvec_add(&b, gpr_strdup("RECV_HALF_CLOSE"));
       break;
     case GRPC_RECV_FINISH:
-      bprintf(&b, "RECV_FINISH");
+      gpr_strvec_add(&b, gpr_strdup("RECV_FINISH"));
       break;
     case GRPC_CANCEL_OP:
-      bprintf(&b, "CANCEL_OP");
+      gpr_strvec_add(&b, gpr_strdup("CANCEL_OP"));
       break;
   }
-  bprintf(&b, " flags=0x%08x", op->flags);
+  gpr_asprintf(&tmp, " flags=0x%08x", op->flags);
+  gpr_strvec_add(&b, tmp);
+
+  out = gpr_strvec_flatten(&b, NULL);
+  gpr_strvec_destroy(&b);
 
-  return b.buffer;
+  return out;
 }
 
 void grpc_call_log_op(char *file, int line, gpr_log_severity severity,
diff --git a/src/core/channel/connected_channel.c b/src/core/channel/connected_channel.c
index 47c0ed3b882bc6246aa11f1a358897ba5a5060ff..55486ed5c34aa64bbabd29d5f4f2dd89d6caa280 100644
--- a/src/core/channel/connected_channel.c
+++ b/src/core/channel/connected_channel.c
@@ -450,6 +450,7 @@ static void recv_batch(void *user_data, grpc_transport *transport,
                    (int)calld->incoming_message.length,
                    (int)calld->incoming_message_length);
       recv_error(chand, calld, __LINE__, message);
+      gpr_free(message);
     }
     call_op.type = GRPC_RECV_HALF_CLOSE;
     call_op.dir = GRPC_CALL_UP;
diff --git a/src/core/httpcli/format_request.c b/src/core/httpcli/format_request.c
index 7a44f1266f1795897149b6c4c7ce3ecb9b13d326..58bb7c740ea8444c862e006f841e012aa5ff5ca3 100644
--- a/src/core/httpcli/format_request.c
+++ b/src/core/httpcli/format_request.c
@@ -37,67 +37,57 @@
 #include <stdio.h>
 #include <string.h>
 
+#include "src/core/support/string.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/slice.h>
 #include <grpc/support/useful.h>
 
-typedef struct {
-  size_t length;
-  size_t capacity;
-  char *data;
-} sbuf;
-
-static void sbuf_append(sbuf *buf, const char *bytes, size_t len) {
-  if (buf->length + len > buf->capacity) {
-    buf->capacity = GPR_MAX(buf->length + len, buf->capacity * 3 / 2);
-    buf->data = gpr_realloc(buf->data, buf->capacity);
-  }
-  memcpy(buf->data + buf->length, bytes, len);
-  buf->length += len;
-}
-
-static void sbprintf(sbuf *buf, const char *fmt, ...) {
-  char temp[GRPC_HTTPCLI_MAX_HEADER_LENGTH];
-  size_t len;
-  va_list args;
-
-  va_start(args, fmt);
-  len = vsprintf(temp, fmt, args);
-  va_end(args);
-
-  sbuf_append(buf, temp, len);
-}
-
-static void fill_common_header(const grpc_httpcli_request *request, sbuf *buf) {
+static void fill_common_header(const grpc_httpcli_request *request, gpr_strvec *buf) {
   size_t i;
-  sbprintf(buf, "%s HTTP/1.0\r\n", request->path);
+  gpr_strvec_add(buf, gpr_strdup(request->path));
+  gpr_strvec_add(buf, gpr_strdup(" HTTP/1.0\r\n"));
   /* just in case some crazy server really expects HTTP/1.1 */
-  sbprintf(buf, "Host: %s\r\n", request->host);
-  sbprintf(buf, "Connection: close\r\n");
-  sbprintf(buf, "User-Agent: %s\r\n", GRPC_HTTPCLI_USER_AGENT);
+  gpr_strvec_add(buf, gpr_strdup("Host: "));
+  gpr_strvec_add(buf, gpr_strdup(request->host));
+  gpr_strvec_add(buf, gpr_strdup("\r\n"));
+  gpr_strvec_add(buf, gpr_strdup("Connection: close\r\n"));
+  gpr_strvec_add(buf, gpr_strdup("User-Agent: "GRPC_HTTPCLI_USER_AGENT"\r\n"));
   /* user supplied headers */
   for (i = 0; i < request->hdr_count; i++) {
-    sbprintf(buf, "%s: %s\r\n", request->hdrs[i].key, request->hdrs[i].value);
+    gpr_strvec_add(buf, gpr_strdup(request->hdrs[i].key));
+    gpr_strvec_add(buf, gpr_strdup(": "));
+    gpr_strvec_add(buf, gpr_strdup(request->hdrs[i].value));
+    gpr_strvec_add(buf, gpr_strdup("\r\n"));
   }
 }
 
 gpr_slice grpc_httpcli_format_get_request(const grpc_httpcli_request *request) {
-  sbuf out = {0, 0, NULL};
+  gpr_strvec out;
+  char *flat;
+  size_t flat_len;
 
-  sbprintf(&out, "GET ");
+  gpr_strvec_init(&out);
+  gpr_strvec_add(&out, gpr_strdup("GET "));
   fill_common_header(request, &out);
-  sbprintf(&out, "\r\n");
+  gpr_strvec_add(&out, gpr_strdup("\r\n"));
 
-  return gpr_slice_new(out.data, out.length, gpr_free);
+  flat = gpr_strvec_flatten(&out, &flat_len);
+  gpr_strvec_destroy(&out);
+
+  return gpr_slice_new(flat, flat_len, gpr_free);
 }
 
 gpr_slice grpc_httpcli_format_post_request(const grpc_httpcli_request *request,
                                            const char *body_bytes,
                                            size_t body_size) {
-  sbuf out = {0, 0, NULL};
+  gpr_strvec out;
+  char *tmp;
+  size_t out_len;
   size_t i;
 
-  sbprintf(&out, "POST ");
+  gpr_strvec_init(&out);
+
+  gpr_strvec_add(&out, gpr_strdup("POST "));
   fill_common_header(request, &out);
   if (body_bytes) {
     gpr_uint8 has_content_type = 0;
@@ -108,14 +98,18 @@ gpr_slice grpc_httpcli_format_post_request(const grpc_httpcli_request *request,
       }
     }
     if (!has_content_type) {
-      sbprintf(&out, "Content-Type: text/plain\r\n");
+      gpr_strvec_add(&out, gpr_strdup("Content-Type: text/plain\r\n"));
     }
-    sbprintf(&out, "Content-Length: %lu\r\n", (unsigned long)body_size);
+    gpr_asprintf(&tmp, "Content-Length: %lu\r\n", (unsigned long)body_size);
+    gpr_strvec_add(&out, tmp);
   }
-  sbprintf(&out, "\r\n");
+  gpr_strvec_add(&out, gpr_strdup("\r\n"));
+  tmp = gpr_strvec_flatten(&out, &out_len);
   if (body_bytes) {
-    sbuf_append(&out, body_bytes, body_size);
+    tmp = gpr_realloc(tmp, out_len + body_size);
+    memcpy(tmp + out_len, body_bytes, body_size);
+    out_len += body_size;
   }
 
-  return gpr_slice_new(out.data, out.length, gpr_free);
+  return gpr_slice_new(tmp, out_len, gpr_free);
 }
diff --git a/src/core/support/string.c b/src/core/support/string.c
index 9b5cac75966fc00080647949b5cd07503be16e7a..97bce60f94cef99ded9f1945c410f090b00dd265 100644
--- a/src/core/support/string.c
+++ b/src/core/support/string.c
@@ -14,7 +14,7 @@
  * 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
+ * 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
@@ -152,3 +152,49 @@ int gpr_ltoa(long value, char *string) {
   string[i] = 0;
   return i;
 }
+
+char *gpr_strjoin(const char **strs, size_t nstrs, size_t *final_length) {
+  size_t out_length = 0;
+  size_t i;
+  char *out;
+  for (i = 0; i < nstrs; i++) {
+    out_length += strlen(strs[i]);
+  }
+  out_length += 1;  /* null terminator */
+  out = gpr_malloc(out_length);
+  out_length = 0;
+  for (i = 0; i < nstrs; i++) {
+    size_t slen = strlen(strs[i]);
+    memcpy(out + out_length, strs[i], slen);
+    out_length += slen;
+  }
+  out[out_length] = 0;
+  if (final_length != NULL) {
+    *final_length = out_length;
+  }
+  return out;
+}
+
+void gpr_strvec_init(gpr_strvec *sv) {
+  memset(sv, 0, sizeof(*sv));
+}
+
+void gpr_strvec_destroy(gpr_strvec *sv) {
+  size_t i;
+  for (i = 0; i < sv->count; i++) {
+    gpr_free(sv->strs[i]);
+  }
+  gpr_free(sv->strs);
+}
+
+void gpr_strvec_add(gpr_strvec *sv, char *str) {
+  if (sv->count == sv->capacity) {
+    sv->capacity = GPR_MAX(sv->capacity + 8, sv->capacity * 2);
+    sv->strs = gpr_realloc(sv->strs, sizeof(char*) * sv->capacity);
+  }
+  sv->strs[sv->count++] = str;
+}
+
+char *gpr_strvec_flatten(gpr_strvec *sv, size_t *final_length) {
+  return gpr_strjoin((const char**)sv->strs, sv->count, final_length);
+}
diff --git a/src/core/support/string.h b/src/core/support/string.h
index 28b7029ecd65c77d36311b8c65ae35e2620ff31c..64e06d3b6aaccb3e04975f5baa78490a93460563 100644
--- a/src/core/support/string.h
+++ b/src/core/support/string.h
@@ -81,6 +81,27 @@ void gpr_reverse_bytes(char *str, int len);
    the result is undefined. */
 int gpr_asprintf(char **strp, const char *format, ...);
 
+/* Join a set of strings, returning the resulting string.
+   Total combined length (excluding null terminator) is returned in total_length
+   if it is non-null. */
+char *gpr_strjoin(const char **strs, size_t nstrs, size_t *total_length);
+
+/* A vector of strings... for building up a final string one piece at a time */
+typedef struct {
+  char **strs;
+  size_t count;
+  size_t capacity;
+} gpr_strvec;
+
+/* Initialize/destroy */
+void gpr_strvec_init(gpr_strvec *strs);
+void gpr_strvec_destroy(gpr_strvec *strs);
+/* Add a string to a strvec, takes ownership of the string */
+void gpr_strvec_add(gpr_strvec *strs, char *add);
+/* Return a joined string with all added substrings, optionally setting
+   total_length as per gpr_strjoin */
+char *gpr_strvec_flatten(gpr_strvec *strs, size_t *total_length);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/core/surface/event_string.c b/src/core/surface/event_string.c
index e38ef06c9ff662f357144296ddb6800274f38391..8975d312eecce726948c72527b18fb46f473a8d4 100644
--- a/src/core/surface/event_string.c
+++ b/src/core/surface/event_string.c
@@ -38,8 +38,10 @@
 #include "src/core/support/string.h"
 #include <grpc/byte_buffer.h>
 
-static size_t addhdr(char *p, grpc_event *ev) {
-  return sprintf(p, "tag:%p call:%p", ev->tag, (void *)ev->call);
+static void addhdr(gpr_strvec *buf, grpc_event *ev) {
+  char *tmp;
+  gpr_asprintf(&tmp, "tag:%p call:%p", ev->tag, (void *)ev->call);
+  gpr_strvec_add(buf, tmp);
 }
 
 static const char *errstr(grpc_op_error err) {
@@ -52,72 +54,84 @@ static const char *errstr(grpc_op_error err) {
   return "UNKNOWN_UNKNOWN";
 }
 
-static size_t adderr(char *p, grpc_op_error err) {
-  return sprintf(p, " err=%s", errstr(err));
+static void adderr(gpr_strvec *buf, grpc_op_error err) {
+  char *tmp;
+  gpr_asprintf(&tmp, " err=%s", errstr(err));
+  gpr_strvec_add(buf, tmp);
 }
 
 char *grpc_event_string(grpc_event *ev) {
-  char buffer[1024];
-  char *p = buffer;
+  char *out;
+  char *tmp;
+  gpr_strvec buf;
 
   if (ev == NULL) return gpr_strdup("null");
 
+  gpr_strvec_init(&buf);
+
   switch (ev->type) {
     case GRPC_SERVER_SHUTDOWN:
-      p += sprintf(p, "SERVER_SHUTDOWN");
+      gpr_strvec_add(&buf, gpr_strdup("SERVER_SHUTDOWN"));
       break;
     case GRPC_QUEUE_SHUTDOWN:
-      p += sprintf(p, "QUEUE_SHUTDOWN");
+      gpr_strvec_add(&buf, gpr_strdup("QUEUE_SHUTDOWN"));
       break;
     case GRPC_READ:
-      p += sprintf(p, "READ: ");
-      p += addhdr(p, ev);
+      gpr_strvec_add(&buf, gpr_strdup("READ: "));
+      addhdr(&buf, ev);
       if (ev->data.read) {
-        p += sprintf(p, " %d bytes",
+        gpr_asprintf(&tmp, " %d bytes",
                      (int)grpc_byte_buffer_length(ev->data.read));
+        gpr_strvec_add(&buf, tmp);
       } else {
-        p += sprintf(p, " end-of-stream");
+        gpr_strvec_add(&buf, gpr_strdup(" end-of-stream"));
       }
       break;
     case GRPC_INVOKE_ACCEPTED:
-      p += sprintf(p, "INVOKE_ACCEPTED: ");
-      p += addhdr(p, ev);
-      p += adderr(p, ev->data.invoke_accepted);
+      gpr_strvec_add(&buf, gpr_strdup("INVOKE_ACCEPTED: "));
+      addhdr(&buf, ev);
+      adderr(&buf, ev->data.invoke_accepted);
       break;
     case GRPC_WRITE_ACCEPTED:
-      p += sprintf(p, "WRITE_ACCEPTED: ");
-      p += addhdr(p, ev);
-      p += adderr(p, ev->data.write_accepted);
+      gpr_strvec_add(&buf, gpr_strdup("WRITE_ACCEPTED: "));
+      addhdr(&buf, ev);
+      adderr(&buf, ev->data.write_accepted);
       break;
     case GRPC_FINISH_ACCEPTED:
-      p += sprintf(p, "FINISH_ACCEPTED: ");
-      p += addhdr(p, ev);
-      p += adderr(p, ev->data.write_accepted);
+      gpr_strvec_add(&buf, gpr_strdup("FINISH_ACCEPTED: "));
+      addhdr(&buf, ev);
+      adderr(&buf, ev->data.write_accepted);
       break;
     case GRPC_CLIENT_METADATA_READ:
-      p += sprintf(p, "CLIENT_METADATA_READ: ");
-      p += addhdr(p, ev);
-      p += sprintf(p, " %d elements", (int)ev->data.client_metadata_read.count);
+      gpr_strvec_add(&buf, gpr_strdup("CLIENT_METADATA_READ: "));
+      addhdr(&buf, ev);
+      gpr_asprintf(&tmp, " %d elements",
+                   (int)ev->data.client_metadata_read.count);
+      gpr_strvec_add(&buf, tmp);
       break;
     case GRPC_FINISHED:
-      p += sprintf(p, "FINISHED: ");
-      p += addhdr(p, ev);
-      p += sprintf(p, " status=%d details='%s' %d metadata elements",
+      gpr_strvec_add(&buf, gpr_strdup("FINISHED: "));
+      addhdr(&buf, ev);
+      gpr_asprintf(&tmp, " status=%d details='%s' %d metadata elements",
                    ev->data.finished.status, ev->data.finished.details,
                    (int)ev->data.finished.metadata_count);
+      gpr_strvec_add(&buf, tmp);
       break;
     case GRPC_SERVER_RPC_NEW:
-      p += sprintf(p, "SERVER_RPC_NEW: ");
-      p += addhdr(p, ev);
-      p += sprintf(p, " method='%s' host='%s' %d metadata elements",
+      gpr_strvec_add(&buf, gpr_strdup("SERVER_RPC_NEW: "));
+      addhdr(&buf, ev);
+      gpr_asprintf(&tmp, " method='%s' host='%s' %d metadata elements",
                    ev->data.server_rpc_new.method, ev->data.server_rpc_new.host,
                    (int)ev->data.server_rpc_new.metadata_count);
+      gpr_strvec_add(&buf, tmp);
       break;
     case GRPC_COMPLETION_DO_NOT_USE:
-      p += sprintf(p, "DO_NOT_USE (this is a bug)");
-      p += addhdr(p, ev);
+      gpr_strvec_add(&buf, gpr_strdup("DO_NOT_USE (this is a bug)"));
+      addhdr(&buf, ev);
       break;
   }
 
-  return gpr_strdup(buffer);
+  out = gpr_strvec_flatten(&buf, NULL);
+  gpr_strvec_destroy(&buf);
+  return out;
 }
diff --git a/src/node/client.js b/src/node/client.js
index 2fefd14bbc5cd8637323072579174ee98c53c5c0..3a1c9eef8467eadc7ecc876c9c3d387023a982a9 100644
--- a/src/node/client.js
+++ b/src/node/client.js
@@ -105,7 +105,7 @@ function GrpcClientStream(call, serialize, deserialize) {
       return;
     }
     var data = event.data;
-    if (self.push(data) && data != null) {
+    if (self.push(self.deserialize(data)) && data != null) {
       self._call.startRead(readCallback);
     } else {
       reading = false;
@@ -155,11 +155,19 @@ GrpcClientStream.prototype._read = function(size) {
  */
 GrpcClientStream.prototype._write = function(chunk, encoding, callback) {
   var self = this;
-  self._call.startWrite(chunk, function(event) {
+  self._call.startWrite(self.serialize(chunk), function(event) {
     callback();
   }, 0);
 };
 
+/**
+ * Cancel the ongoing call. If the call has not already finished, it will finish
+ * with status CANCELLED.
+ */
+GrpcClientStream.prototype.cancel = function() {
+  this._call.cancel();
+};
+
 /**
  * Make a request on the channel to the given method with the given arguments
  * @param {grpc.Channel} channel The channel on which to make the request
@@ -185,7 +193,7 @@ function makeRequest(channel,
   if (metadata) {
     call.addMetadata(metadata);
   }
-  return new GrpcClientStream(call);
+  return new GrpcClientStream(call, serialize, deserialize);
 }
 
 /**
diff --git a/src/node/examples/math_server.js b/src/node/examples/math_server.js
index d649b4fd6d0749ad74c2ce319c9bcf0941843f17..e65cfe3002784fc8315c7beb2fd89a229d8557e4 100644
--- a/src/node/examples/math_server.js
+++ b/src/node/examples/math_server.js
@@ -52,7 +52,8 @@ var Server = grpc.buildServer([math.Math.service]);
  */
 function mathDiv(call, cb) {
   var req = call.request;
-  if (req.divisor == 0) {
+  // Unary + is explicit coersion to integer
+  if (+req.divisor === 0) {
     cb(new Error('cannot divide by zero'));
   }
   cb(null, {
@@ -89,7 +90,7 @@ function mathSum(call, cb) {
   // Here, call is a standard readable Node object Stream
   var sum = 0;
   call.on('data', function(data) {
-    sum += data.num | 0;
+    sum += (+data.num);
   });
   call.on('end', function() {
     cb(null, {num: sum});
@@ -104,7 +105,7 @@ function mathDivMany(stream) {
     Transform.call(this, options);
   }
   DivTransform.prototype._transform = function(div_args, encoding, callback) {
-    if (div_args.divisor == 0) {
+    if (+div_args.divisor === 0) {
       callback(new Error('cannot divide by zero'));
     }
     callback(null, {
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index cf75b9a77ac91e419d835652af1cdbe8b6e7cecb..9306317b68fd481551c4a0cd51099605f045666c 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -183,7 +183,7 @@ function pingPong(client, done) {
     assert.equal(response.payload.body.limit - response.payload.body.offset,
                  response_sizes[index]);
     index += 1;
-    if (index == 4) {
+    if (index === 4) {
       call.end();
     } else {
       call.write({
diff --git a/src/node/server.js b/src/node/server.js
index eca20aa5fd070fa000dbb93a7fd8340b1e33658a..03cdbe6f9849d4e0225900ba0811d1ca28652d81 100644
--- a/src/node/server.js
+++ b/src/node/server.js
@@ -151,7 +151,7 @@ function GrpcServerStream(call, serialize, deserialize) {
       return;
     }
     var data = event.data;
-    if (self.push(deserialize(data)) && data != null) {
+    if (self.push(self.deserialize(data)) && data != null) {
       self._call.startRead(readCallback);
     } else {
       reading = false;
@@ -233,7 +233,7 @@ function Server(options) {
     function handleNewCall(event) {
       var call = event.call;
       var data = event.data;
-      if (data == null) {
+      if (data === null) {
         return;
       }
       server.requestCall(handleNewCall);
@@ -246,6 +246,7 @@ function Server(options) {
       call.serverAccept(function(event) {
         if (event.data.code === grpc.status.CANCELLED) {
           cancelled = true;
+          stream.emit('cancelled');
         }
       }, 0);
       call.serverEndInitialMetadata(0);
diff --git a/src/node/surface_client.js b/src/node/surface_client.js
index 996e3d101fcd94556585aa722940bc80520196b3..16c31809f43a07b390a66183f9e21b47e52cbd46 100644
--- a/src/node/surface_client.js
+++ b/src/node/surface_client.js
@@ -63,114 +63,80 @@ util.inherits(ClientReadableObjectStream, Readable);
  * client side. Extends from stream.Readable.
  * @constructor
  * @param {stream} stream Underlying binary Duplex stream for the call
- * @param {function(Buffer)} deserialize Function for deserializing binary data
- * @param {object} options Stream options
  */
-function ClientReadableObjectStream(stream, deserialize, options) {
-  options = _.extend(options, {objectMode: true});
+function ClientReadableObjectStream(stream) {
+  var options = {objectMode: true};
   Readable.call(this, options);
   this._stream = stream;
   var self = this;
   forwardEvent(stream, this, 'status');
   forwardEvent(stream, this, 'metadata');
   this._stream.on('data', function forwardData(chunk) {
-    if (!self.push(deserialize(chunk))) {
+    if (!self.push(chunk)) {
       self._stream.pause();
     }
   });
   this._stream.pause();
 }
 
-util.inherits(ClientWritableObjectStream, Writable);
-
 /**
- * Class for representing a gRPC client streaming call as a Node stream on the
- * client side. Extends from stream.Writable.
- * @constructor
- * @param {stream} stream Underlying binary Duplex stream for the call
- * @param {function(*):Buffer} serialize Function for serializing objects
- * @param {object} options Stream options
+ * _read implementation for both types of streams that allow reading.
+ * @this {ClientReadableObjectStream}
+ * @param {number} size Ignored
  */
-function ClientWritableObjectStream(stream, serialize, options) {
-  options = _.extend(options, {objectMode: true});
-  Writable.call(this, options);
-  this._stream = stream;
-  this._serialize = serialize;
-  forwardEvent(stream, this, 'status');
-  forwardEvent(stream, this, 'metadata');
-  this.on('finish', function() {
-    this._stream.end();
-  });
+function _read(size) {
+  this._stream.resume();
 }
 
+/**
+ * See docs for _read
+ */
+ClientReadableObjectStream.prototype._read = _read;
 
-util.inherits(ClientBidiObjectStream, Duplex);
+util.inherits(ClientWritableObjectStream, Writable);
 
 /**
- * Class for representing a gRPC bidi streaming call as a Node stream on the
- * client side. Extends from stream.Duplex.
+ * Class for representing a gRPC client streaming call as a Node stream on the
+ * client side. Extends from stream.Writable.
  * @constructor
  * @param {stream} stream Underlying binary Duplex stream for the call
- * @param {function(*):Buffer} serialize Function for serializing objects
- * @param {function(Buffer)} deserialize Function for deserializing binary data
- * @param {object} options Stream options
  */
-function ClientBidiObjectStream(stream, serialize, deserialize, options) {
-  options = _.extend(options, {objectMode: true});
-  Duplex.call(this, options);
+function ClientWritableObjectStream(stream) {
+  var options = {objectMode: true};
+  Writable.call(this, options);
   this._stream = stream;
-  this._serialize = serialize;
-  var self = this;
   forwardEvent(stream, this, 'status');
   forwardEvent(stream, this, 'metadata');
-  this._stream.on('data', function forwardData(chunk) {
-    if (!self.push(deserialize(chunk))) {
-      self._stream.pause();
-    }
-  });
-  this._stream.pause();
   this.on('finish', function() {
     this._stream.end();
   });
 }
 
-/**
- * _read implementation for both types of streams that allow reading.
- * @this {ClientReadableObjectStream|ClientBidiObjectStream}
- * @param {number} size Ignored
- */
-function _read(size) {
-  this._stream.resume();
-}
-
-/**
- * See docs for _read
- */
-ClientReadableObjectStream.prototype._read = _read;
-/**
- * See docs for _read
- */
-ClientBidiObjectStream.prototype._read = _read;
-
 /**
  * _write implementation for both types of streams that allow writing
- * @this {ClientWritableObjectStream|ClientBidiObjectStream}
+ * @this {ClientWritableObjectStream}
  * @param {*} chunk The value to write to the stream
  * @param {string} encoding Ignored
  * @param {function(Error)} callback Callback to call when finished writing
  */
 function _write(chunk, encoding, callback) {
-  this._stream.write(this._serialize(chunk), encoding, callback);
+  this._stream.write(chunk, encoding, callback);
 }
 
 /**
  * See docs for _write
  */
 ClientWritableObjectStream.prototype._write = _write;
+
 /**
- * See docs for _write
+ * Cancel the underlying call
  */
-ClientBidiObjectStream.prototype._write = _write;
+function cancel() {
+  this._stream.cancel();
+}
+
+ClientReadableObjectStream.prototype.cancel = cancel;
+ClientWritableObjectStream.prototype.cancel = cancel;
 
 /**
  * Get a function that can make unary requests to the specified method.
@@ -196,19 +162,28 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
    * @return {EventEmitter} An event emitter for stream related events
    */
   function makeUnaryRequest(argument, callback, metadata, deadline) {
-    var stream = client.makeRequest(this.channel, method, metadata, deadline);
+    var stream = client.makeRequest(this.channel, method, serialize,
+                                    deserialize, metadata, deadline);
     var emitter = new EventEmitter();
+    emitter.cancel = function cancel() {
+      stream.cancel();
+    };
     forwardEvent(stream, emitter, 'status');
     forwardEvent(stream, emitter, 'metadata');
-    stream.write(serialize(argument));
+    stream.write(argument);
     stream.end();
     stream.on('data', function forwardData(chunk) {
       try {
-        callback(null, deserialize(chunk));
+        callback(null, chunk);
       } catch (e) {
         callback(e);
       }
     });
+    stream.on('status', function forwardStatus(status) {
+      if (status.code !== client.status.OK) {
+        callback(status);
+      }
+    });
     return emitter;
   }
   return makeUnaryRequest;
@@ -236,15 +211,21 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
    * @return {EventEmitter} An event emitter for stream related events
    */
   function makeClientStreamRequest(callback, metadata, deadline) {
-    var stream = client.makeRequest(this.channel, method, metadata, deadline);
-    var obj_stream = new ClientWritableObjectStream(stream, serialize, {});
+    var stream = client.makeRequest(this.channel, method, serialize,
+                                    deserialize, metadata, deadline);
+    var obj_stream = new ClientWritableObjectStream(stream);
     stream.on('data', function forwardData(chunk) {
       try {
-        callback(null, deserialize(chunk));
+        callback(null, chunk);
       } catch (e) {
         callback(e);
       }
     });
+    stream.on('status', function forwardStatus(status) {
+      if (status.code !== client.status.OK) {
+        callback(status);
+      }
+    });
     return obj_stream;
   }
   return makeClientStreamRequest;
@@ -272,9 +253,10 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
    * @return {EventEmitter} An event emitter for stream related events
    */
   function makeServerStreamRequest(argument, metadata, deadline) {
-    var stream = client.makeRequest(this.channel, method, metadata, deadline);
-    var obj_stream = new ClientReadableObjectStream(stream, deserialize, {});
-    stream.write(serialize(argument));
+    var stream = client.makeRequest(this.channel, method, serialize,
+                                    deserialize, metadata, deadline);
+    var obj_stream = new ClientReadableObjectStream(stream);
+    stream.write(argument);
     stream.end();
     return obj_stream;
   }
@@ -301,12 +283,8 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
    * @return {EventEmitter} An event emitter for stream related events
    */
   function makeBidiStreamRequest(metadata, deadline) {
-    var stream = client.makeRequest(this.channel, method, metadata, deadline);
-    var obj_stream = new ClientBidiObjectStream(stream,
-                                                serialize,
-                                                deserialize,
-                                                {});
-    return obj_stream;
+    return client.makeRequest(this.channel, method, serialize,
+                              deserialize, metadata, deadline);
   }
   return makeBidiStreamRequest;
 }
diff --git a/src/node/surface_server.js b/src/node/surface_server.js
index bc688839fe56ed017e2b4d8eb738891382d0b5a0..af23ec211cdddcb12781befafca98a3faaf5e6e4 100644
--- a/src/node/surface_server.js
+++ b/src/node/surface_server.js
@@ -54,67 +54,20 @@ util.inherits(ServerReadableObjectStream, Readable);
  * server side. Extends from stream.Readable.
  * @constructor
  * @param {stream} stream Underlying binary Duplex stream for the call
- * @param {function(Buffer)} deserialize Function for deserializing binary data
- * @param {object} options Stream options
  */
-function ServerReadableObjectStream(stream, deserialize, options) {
-  options = _.extend(options, {objectMode: true});
+function ServerReadableObjectStream(stream) {
+  var options = {objectMode: true};
   Readable.call(this, options);
   this._stream = stream;
   Object.defineProperty(this, 'cancelled', {
     get: function() { return stream.cancelled; }
   });
   var self = this;
-  this._stream.on('data', function forwardData(chunk) {
-    if (!self.push(deserialize(chunk))) {
-      self._stream.pause();
-    }
-  });
-  this._stream.on('end', function forwardEnd() {
-    self.push(null);
+  this._stream.on('cancelled', function() {
+    self.emit('cancelled');
   });
-  this._stream.pause();
-}
-
-util.inherits(ServerWritableObjectStream, Writable);
-
-/**
- * Class for representing a gRPC server streaming call as a Node stream on the
- * server side. Extends from stream.Writable.
- * @constructor
- * @param {stream} stream Underlying binary Duplex stream for the call
- * @param {function(*):Buffer} serialize Function for serializing objects
- * @param {object} options Stream options
- */
-function ServerWritableObjectStream(stream, serialize, options) {
-  options = _.extend(options, {objectMode: true});
-  Writable.call(this, options);
-  this._stream = stream;
-  this._serialize = serialize;
-  this.on('finish', function() {
-    this._stream.end();
-  });
-}
-
-util.inherits(ServerBidiObjectStream, Duplex);
-
-/**
- * Class for representing a gRPC bidi streaming call as a Node stream on the
- * server side. Extends from stream.Duplex.
- * @constructor
- * @param {stream} stream Underlying binary Duplex stream for the call
- * @param {function(*):Buffer} serialize Function for serializing objects
- * @param {function(Buffer)} deserialize Function for deserializing binary data
- * @param {object} options Stream options
- */
-function ServerBidiObjectStream(stream, serialize, deserialize, options) {
-  options = _.extend(options, {objectMode: true});
-  Duplex.call(this, options);
-  this._stream = stream;
-  this._serialize = serialize;
-  var self = this;
   this._stream.on('data', function forwardData(chunk) {
-    if (!self.push(deserialize(chunk))) {
+    if (!self.push(chunk)) {
       self._stream.pause();
     }
   });
@@ -122,9 +75,6 @@ function ServerBidiObjectStream(stream, serialize, deserialize, options) {
     self.push(null);
   });
   this._stream.pause();
-  this.on('finish', function() {
-    this._stream.end();
-  });
 }
 
 /**
@@ -140,39 +90,49 @@ function _read(size) {
  * See docs for _read
  */
 ServerReadableObjectStream.prototype._read = _read;
+
+util.inherits(ServerWritableObjectStream, Writable);
+
 /**
- * See docs for _read
+ * Class for representing a gRPC server streaming call as a Node stream on the
+ * server side. Extends from stream.Writable.
+ * @constructor
+ * @param {stream} stream Underlying binary Duplex stream for the call
  */
-ServerBidiObjectStream.prototype._read = _read;
+function ServerWritableObjectStream(stream) {
+  var options = {objectMode: true};
+  Writable.call(this, options);
+  this._stream = stream;
+  this._stream.on('cancelled', function() {
+    self.emit('cancelled');
+  });
+  this.on('finish', function() {
+    this._stream.end();
+  });
+}
 
 /**
  * _write implementation for both types of streams that allow writing
- * @this {ServerWritableObjectStream|ServerBidiObjectStream}
+ * @this {ServerWritableObjectStream}
  * @param {*} chunk The value to write to the stream
  * @param {string} encoding Ignored
  * @param {function(Error)} callback Callback to call when finished writing
  */
 function _write(chunk, encoding, callback) {
-  this._stream.write(this._serialize(chunk), encoding, callback);
+  this._stream.write(chunk, encoding, callback);
 }
 
 /**
  * See docs for _write
  */
 ServerWritableObjectStream.prototype._write = _write;
-/**
- * See docs for _write
- */
-ServerBidiObjectStream.prototype._write = _write;
 
 /**
  * Creates a binary stream handler function from a unary handler function
  * @param {function(Object, function(Error, *))} handler Unary call handler
- * @param {function(*):Buffer} serialize Serialization function
- * @param {function(Buffer):*} deserialize Deserialization function
  * @return {function(stream)} Binary stream handler
  */
-function makeUnaryHandler(handler, serialize, deserialize) {
+function makeUnaryHandler(handler) {
   /**
    * Handles a stream by reading a single data value, passing it to the handler,
    * and writing the response back to the stream.
@@ -180,15 +140,18 @@ function makeUnaryHandler(handler, serialize, deserialize) {
    */
   return function handleUnaryCall(stream) {
     stream.on('data', function handleUnaryData(value) {
-      var call = {request: deserialize(value)};
+      var call = {request: value};
       Object.defineProperty(call, 'cancelled', {
         get: function() { return stream.cancelled;}
       });
+      stream.on('cancelled', function() {
+        call.emit('cancelled');
+      });
       handler(call, function sendUnaryData(err, value) {
         if (err) {
           stream.emit('error', err);
         } else {
-          stream.write(serialize(value));
+          stream.write(value);
           stream.end();
         }
       });
@@ -201,23 +164,21 @@ function makeUnaryHandler(handler, serialize, deserialize) {
  * function
  * @param {function(Readable, function(Error, *))} handler Client stream call
  *     handler
- * @param {function(*):Buffer} serialize Serialization function
- * @param {function(Buffer):*} deserialize Deserialization function
  * @return {function(stream)} Binary stream handler
  */
-function makeClientStreamHandler(handler, serialize, deserialize) {
+function makeClientStreamHandler(handler) {
   /**
    * Handles a stream by passing a deserializing stream to the handler and
    * writing the response back to the stream.
    * @param {stream} stream Binary data stream
    */
   return function handleClientStreamCall(stream) {
-    var object_stream = new ServerReadableObjectStream(stream, deserialize, {});
+    var object_stream = new ServerReadableObjectStream(stream);
     handler(object_stream, function sendClientStreamData(err, value) {
         if (err) {
           stream.emit('error', err);
         } else {
-          stream.write(serialize(value));
+          stream.write(value);
           stream.end();
         }
     });
@@ -228,11 +189,9 @@ function makeClientStreamHandler(handler, serialize, deserialize) {
  * Creates a binary stream handler function from a server stream handler
  * function
  * @param {function(Writable)} handler Server stream call handler
- * @param {function(*):Buffer} serialize Serialization function
- * @param {function(Buffer):*} deserialize Deserialization function
  * @return {function(stream)} Binary stream handler
  */
-function makeServerStreamHandler(handler, serialize, deserialize) {
+function makeServerStreamHandler(handler) {
   /**
    * Handles a stream by attaching it to a serializing stream, and passing it to
    * the handler.
@@ -240,10 +199,8 @@ function makeServerStreamHandler(handler, serialize, deserialize) {
    */
   return function handleServerStreamCall(stream) {
     stream.on('data', function handleClientData(value) {
-      var object_stream = new ServerWritableObjectStream(stream,
-                                                         serialize,
-                                                         {});
-      object_stream.request = deserialize(value);
+      var object_stream = new ServerWritableObjectStream(stream);
+      object_stream.request = value;
       handler(object_stream);
     });
   };
@@ -252,23 +209,10 @@ function makeServerStreamHandler(handler, serialize, deserialize) {
 /**
  * Creates a binary stream handler function from a bidi stream handler function
  * @param {function(Duplex)} handler Unary call handler
- * @param {function(*):Buffer} serialize Serialization function
- * @param {function(Buffer):*} deserialize Deserialization function
  * @return {function(stream)} Binary stream handler
  */
-function makeBidiStreamHandler(handler, serialize, deserialize) {
-  /**
-   * Handles a stream by wrapping it in a serializing and deserializing object
-   * stream, and passing it to the handler.
-   * @param {stream} stream Binary data stream
-   */
-  return function handleBidiStreamCall(stream) {
-    var object_stream = new ServerBidiObjectStream(stream,
-                                                   serialize,
-                                                   deserialize,
-                                                   {});
-    handler(object_stream);
-  };
+function makeBidiStreamHandler(handler) {
+  return handler;
 }
 
 /**
@@ -341,10 +285,13 @@ function makeServerConstructor(services) {
               common.fullyQualifiedName(method) + ' not provided.');
         }
         var binary_handler = handler_makers[method_type](
-            service_handlers[service_name][decapitalize(method.name)],
-            common.serializeCls(method.resolvedResponseType.build()),
-            common.deserializeCls(method.resolvedRequestType.build()));
-        server.register(prefix + capitalize(method.name), binary_handler);
+            service_handlers[service_name][decapitalize(method.name)]);
+        var serialize = common.serializeCls(
+            method.resolvedResponseType.build());
+        var deserialize = common.deserializeCls(
+            method.resolvedRequestType.build());
+        server.register(prefix + capitalize(method.name), binary_handler,
+                        serialize, deserialize);
       });
     }, this);
   }
diff --git a/src/node/test/client_server_test.js b/src/node/test/client_server_test.js
index 2a25908684be8d36f813077e1fcb6349de6ac8df..99438a165925a0d077171fb337b83a1733497bc1 100644
--- a/src/node/test/client_server_test.js
+++ b/src/node/test/client_server_test.js
@@ -77,6 +77,14 @@ function errorHandler(stream) {
   };
 }
 
+/**
+ * Wait for a cancellation instead of responding
+ * @param {Stream} stream
+ */
+function cancelHandler(stream) {
+  // do nothing
+}
+
 describe('echo client', function() {
   it('should receive echo responses', function(done) {
     var server = new Server();
@@ -125,6 +133,26 @@ describe('echo client', function() {
       done();
     });
   });
+  it('should be able to cancel a call', function(done) {
+    var server = new Server();
+    var port_num = server.bind('0.0.0.0:0');
+    server.register('cancellation', cancelHandler);
+    server.start();
+
+    var channel = new grpc.Channel('localhost:' + port_num);
+    var stream = client.makeRequest(
+        channel,
+        'cancellation',
+        null,
+        getDeadline(1));
+
+    stream.cancel();
+    stream.on('status', function(status) {
+      assert.equal(status.code, grpc.status.CANCELLED);
+      server.shutdown();
+      done();
+    });
+  });
 });
 /* TODO(mlumish): explore options for reducing duplication between this test
  * and the insecure echo client test */
diff --git a/src/node/test/interop_sanity_test.js b/src/node/test/interop_sanity_test.js
index 3c062b978820800b2b2e3cd818758ee22850ae0b..6cc7d444cdc33272756318bbbc679d0bc6b06747 100644
--- a/src/node/test/interop_sanity_test.js
+++ b/src/node/test/interop_sanity_test.js
@@ -48,6 +48,9 @@ describe('Interop tests', function() {
     port = 'localhost:' + server_obj.port;
     done();
   });
+  after(function() {
+    server.shutdown();
+  });
   // This depends on not using a binary stream
   it('should pass empty_unary', function(done) {
     interop_client.runTest(port, name_override, 'empty_unary', true, done);
@@ -65,7 +68,7 @@ describe('Interop tests', function() {
   it('should pass ping_pong', function(done) {
     interop_client.runTest(port, name_override, 'ping_pong', true, done);
   });
-  it.skip('should pass empty_stream', function(done) {
+  it('should pass empty_stream', function(done) {
     interop_client.runTest(port, name_override, 'empty_stream', true, done);
   });
 });
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index 34f1a156eb99c62b19d2ca4f6c0feddb9adb5264..16e4869d83e58b86252b75c2c931cc85ba69376c 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -35,6 +35,8 @@ var assert = require('assert');
 
 var surface_server = require('../surface_server.js');
 
+var surface_client = require('../surface_client.js');
+
 var ProtoBuf = require('protobufjs');
 
 var grpc = require('..');
@@ -73,3 +75,54 @@ describe('Surface server constructor', function() {
     }, /math.Math/);
   });
 });
+describe('Surface client', function() {
+  var client;
+  var server;
+  before(function() {
+    var Server = grpc.buildServer([mathService]);
+    server = new Server({
+      'math.Math': {
+        'div': function(stream) {},
+        'divMany': function(stream) {},
+        'fib': function(stream) {},
+        'sum': function(stream) {}
+      }
+    });
+    var port = server.bind('localhost:0');
+    var Client = surface_client.makeClientConstructor(mathService);
+    client = new Client('localhost:' + port);
+  });
+  after(function() {
+    server.shutdown();
+  });
+  it('Should correctly cancel a unary call', function(done) {
+    var call = client.div({'divisor': 0, 'dividend': 0}, function(err, resp) {
+      assert.strictEqual(err.code, surface_client.status.CANCELLED);
+      done();
+    });
+    call.cancel();
+  });
+  it('Should correctly cancel a client stream call', function(done) {
+    var call = client.sum(function(err, resp) {
+      assert.strictEqual(err.code, surface_client.status.CANCELLED);
+      done();
+    });
+    call.cancel();
+  });
+  it('Should correctly cancel a server stream call', function(done) {
+    var call = client.fib({'limit': 5});
+    call.on('status', function(status) {
+      assert.strictEqual(status.code, surface_client.status.CANCELLED);
+      done();
+    });
+    call.cancel();
+  });
+  it('Should correctly cancel a bidi stream call', function(done) {
+    var call = client.divMany();
+    call.on('status', function(status) {
+      assert.strictEqual(status.code, surface_client.status.CANCELLED);
+      done();
+    });
+    call.cancel();
+  });
+});
diff --git a/src/ruby/README.md b/src/ruby/README.md
index 7f7558dc6770b56d3bc42aee30c03009d464393a..7ece7e27065f811ee2c9f22363c57e2ac8288b8e 100755
--- a/src/ruby/README.md
+++ b/src/ruby/README.md
@@ -14,9 +14,10 @@ INSTALLING
 ----------
 
 - Install the gRPC core library
-TODO: describe this, once the core distribution mechanism is defined.
-
+  TODO: describe this, once the core distribution mechanism is defined.
+```
 $ gem install grpc
+```
 
 
 Installing from source
@@ -24,37 +25,47 @@ Installing from source
 
 - Build or Install the gRPC core
 E.g, from the root of the grpc [git repo](https://github.com/google/grpc)
+```
 $ cd ../..
 $ make && sudo make install
+```
 
 - Install Ruby 2.x. Consider doing this with [RVM](http://rvm.io), it's a nice way of controlling
   the exact ruby version that's used.
+```
 $ command curl -sSL https://rvm.io/mpapis.asc | gpg --import -
 $ \curl -sSL https://get.rvm.io | bash -s stable --ruby
 $
 $ # follow the instructions to ensure that your're using the latest stable version of Ruby
 $ # and that the rvm command is installed
+```
 
 - Install [bundler](http://bundler.io/)
+```
 $ gem install bundler
+```
 
 - Finally, install grpc ruby locally.
+```
 $ cd <install_dir>
 $ bundle install
 $ rake  # compiles the extension, runs the unit tests, see rake -T for other options
-
+```
 
 CONTENTS
 --------
 
 Directory structure is the layout for [ruby extensions](http://guides.rubygems.org/gems-with-extensions/)
 
- * ext: the extension code
- * lib: the entrypoint grpc ruby library to be used in a 'require' statement
- * spec: tests
- * bin: example gRPC clients and servers, e.g,
+- ext:
+  the gRPC ruby extension
+- lib:
+  the entrypoint grpc ruby library to be used in a 'require' statement
+- spec:
+  Rspec unittest
+- bin:
+  example gRPC clients and servers, e.g,
 ```ruby
-# client
 stub = Math::Math::Stub.new('my.test.math.server.com:8080')
 req = Math::DivArgs.new(dividend: 7, divisor: 3)
 logger.info("div(7/3): req=#{req.inspect}")
diff --git a/src/ruby/spec/client_server_spec.rb b/src/ruby/spec/client_server_spec.rb
index c2517d649fe7ae0e5549f96e84079f700e5b04ca..567206ea6f8639729bc73058bb0782f00a7a755b 100644
--- a/src/ruby/spec/client_server_spec.rb
+++ b/src/ruby/spec/client_server_spec.rb
@@ -44,12 +44,13 @@ shared_context 'setup: tags' do
   before(:example) do
     @server_finished_tag = Object.new
     @client_finished_tag = Object.new
+    @client_metadata_tag = Object.new
     @server_tag = Object.new
     @tag = Object.new
   end
 
   def deadline
-    Time.now + 0.05
+    Time.now + 2
   end
 
   def expect_next_event_on(queue, type, tag)
@@ -63,27 +64,30 @@ shared_context 'setup: tags' do
     ev
   end
 
-  def server_receives_and_responds_with(reply_text)
-    reply = ByteBuffer.new(reply_text)
+  def server_allows_client_to_proceed
     @server.request_call(@server_tag)
-    ev = @server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE)
+    ev = @server_queue.pluck(@server_tag, deadline)
     expect(ev).not_to be_nil
     expect(ev.type).to be(SERVER_RPC_NEW)
-    ev.call.server_accept(@server_queue, @server_finished_tag)
-    ev.call.server_end_initial_metadata
-    ev.call.start_read(@server_tag)
+    server_call = ev.call
+    server_call.server_accept(@server_queue, @server_finished_tag)
+    server_call.server_end_initial_metadata
+    server_call
+  end
+
+  def server_responds_with(server_call, reply_text)
+    reply = ByteBuffer.new(reply_text)
+    server_call.start_read(@server_tag)
     ev = @server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE)
     expect(ev.type).to be(READ)
-    ev.call.start_write(reply, @server_tag)
+    server_call.start_write(reply, @server_tag)
     ev = @server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE)
     expect(ev).not_to be_nil
     expect(ev.type).to be(WRITE_ACCEPTED)
-    ev.call
   end
 
   def client_sends(call, sent = 'a message')
     req = ByteBuffer.new(sent)
-    call.invoke(@client_queue,  @tag, @client_finished_tag)
     call.start_write(req, @tag)
     ev = @client_queue.pluck(@tag, TimeConsts::INFINITE_FUTURE)
     expect(ev).not_to be_nil
@@ -102,16 +106,20 @@ shared_examples 'basic GRPC message delivery is OK' do
   it 'servers receive requests from clients and start responding' do
     reply = ByteBuffer.new('the server payload')
     call = new_client_call
-    msg = client_sends(call)
+    call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
 
     # check the server rpc new was received
-    @server.request_call(@server_tag)
-    ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+    # @server.request_call(@server_tag)
+    # ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
 
     # accept the call
-    server_call = ev.call
-    server_call.server_accept(@server_queue, @server_finished_tag)
-    server_call.server_end_initial_metadata
+    # server_call = ev.call
+    # server_call.server_accept(@server_queue, @server_finished_tag)
+    # server_call.server_end_initial_metadata
+    server_call = server_allows_client_to_proceed
+
+    # client sends a message
+    msg = client_sends(call)
 
     # confirm the server can read the inbound message
     server_call.start_read(@server_tag)
@@ -125,18 +133,19 @@ shared_examples 'basic GRPC message delivery is OK' do
 
   it 'responses written by servers are received by the client' do
     call = new_client_call
+    call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
+    server_call = server_allows_client_to_proceed
     client_sends(call)
-    server_receives_and_responds_with('server_response')
+    server_responds_with(server_call, 'server_response')
 
     call.start_read(@tag)
-    expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
     ev = expect_next_event_on(@client_queue, READ, @tag)
     expect(ev.result.to_s).to eq('server_response')
   end
 
   it 'servers can ignore a client write and send a status' do
     call = new_client_call
-    client_sends(call)
+    call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
 
     # check the server rpc new was received
     @server.request_call(@server_tag)
@@ -150,9 +159,13 @@ shared_examples 'basic GRPC message delivery is OK' do
     server_call.start_write_status(StatusCodes::NOT_FOUND, 'not found',
                                    @server_tag)
 
+    # Client sends some data
+    client_sends(call)
+
     # client gets an empty response for the read, preceeded by some metadata.
     call.start_read(@tag)
-    expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+    expect_next_event_on(@client_queue, CLIENT_METADATA_READ,
+                         @client_metadata_tag)
     ev = expect_next_event_on(@client_queue, READ, @tag)
     expect(ev.tag).to be(@tag)
     expect(ev.result.to_s).to eq('')
@@ -166,13 +179,14 @@ shared_examples 'basic GRPC message delivery is OK' do
 
   it 'completes calls by sending status to client and server' do
     call = new_client_call
+    call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
+    server_call = server_allows_client_to_proceed
     client_sends(call)
-    server_call = server_receives_and_responds_with('server_response')
+    server_responds_with(server_call, 'server_response')
     server_call.start_write_status(10_101, 'status code is 10101', @server_tag)
 
     # first the client says writes are done
     call.start_read(@tag)
-    expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
     expect_next_event_on(@client_queue, READ, @tag)
     call.writes_done(@tag)
 
@@ -215,22 +229,13 @@ shared_examples 'GRPC metadata delivery works OK' do
       end
     end
 
-    it 'sends an empty hash when no metadata is added' do
-      call = new_client_call
-      client_sends(call)
-
-      # Server gets a response
-      @server.request_call(@server_tag)
-      expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
-    end
-
     it 'sends all the metadata pairs when keys and values are valid' do
       @valid_metadata.each do |md|
         call = new_client_call
         call.add_metadata(md)
 
         # Client begins a call OK
-        call.invoke(@client_queue, @tag, @client_finished_tag)
+        call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
 
         # ... server has all metadata available even though the client did not
         # send a write
@@ -262,7 +267,7 @@ shared_examples 'GRPC metadata delivery works OK' do
     it 'raises an exception if a metadata key is invalid' do
       @bad_keys.each do |md|
         call = new_client_call
-        client_sends(call)
+        call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
 
         # server gets the invocation
         @server.request_call(@server_tag)
@@ -273,7 +278,7 @@ shared_examples 'GRPC metadata delivery works OK' do
 
     it 'sends a hash that contains the status when no metadata is added' do
       call = new_client_call
-      client_sends(call)
+      call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
 
       # server gets the invocation
       @server.request_call(@server_tag)
@@ -284,21 +289,17 @@ shared_examples 'GRPC metadata delivery works OK' do
       server_call.server_accept(@server_queue, @server_finished_tag)
       server_call.server_end_initial_metadata
 
-      # ... these server sends some data, allowing the metadata read
-      server_call.start_write(ByteBuffer.new('reply with metadata'),
-                              @server_tag)
-      expect_next_event_on(@server_queue, WRITE_ACCEPTED, @server_tag)
-
       # there is the HTTP status metadata, though there should not be any
       # TODO: update this with the bug number to be resolved
-      ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+      ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ,
+                                @client_metadata_tag)
       expect(ev.result).to eq(':status' => '200')
     end
 
     it 'sends all the pairs and status:200 when keys and values are valid' do
       @valid_metadata.each do |md|
         call = new_client_call
-        client_sends(call)
+        call.invoke(@client_queue, @client_metadata_tag, @client_finished_tag)
 
         # server gets the invocation
         @server.request_call(@server_tag)
@@ -311,7 +312,8 @@ shared_examples 'GRPC metadata delivery works OK' do
         server_call.server_end_initial_metadata
 
         # Now the client can read the metadata
-        ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+        ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ,
+                                  @client_metadata_tag)
         replace_symbols = Hash[md.each_pair.collect { |x, y| [x.to_s, y] }]
         replace_symbols[':status'] = '200'
         expect(ev.result).to eq(replace_symbols)
@@ -322,16 +324,17 @@ end
 
 describe 'the http client/server' do
   before(:example) do
-    server_host = 'localhost:0'
+    server_host = '0.0.0.0:0'
     @client_queue = GRPC::Core::CompletionQueue.new
     @server_queue = GRPC::Core::CompletionQueue.new
     @server = GRPC::Core::Server.new(@server_queue, nil)
     server_port = @server.add_http2_port(server_host)
     @server.start
-    @ch = Channel.new("localhost:#{server_port}", nil)
+    @ch = Channel.new("0.0.0.0:#{server_port}", nil)
   end
 
   after(:example) do
+    @ch.close
     @server.close
   end
 
@@ -345,16 +348,15 @@ end
 describe 'the secure http client/server' do
   before(:example) do
     certs = load_test_certs
-    port = find_unused_tcp_port
     server_host = 'localhost:0'
     @client_queue = GRPC::Core::CompletionQueue.new
     @server_queue = GRPC::Core::CompletionQueue.new
     server_creds = GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2])
     @server = GRPC::Core::Server.new(@server_queue, nil, server_creds)
-    server_port =  @server.add_http2_port(host, true)
+    server_port = @server.add_http2_port(server_host, true)
     @server.start
     args = { Channel::SSL_TARGET => 'foo.test.google.com' }
-    @ch = Channel.new("localhost:#{server_port}", args,
+    @ch = Channel.new("0.0.0.0:#{server_port}", args,
                       GRPC::Core::Credentials.new(certs[0], nil, nil))
   end
 
diff --git a/test/core/channel/metadata_buffer_test.c b/test/core/channel/metadata_buffer_test.c
index aa2399272f7a35be286c45b346c75edda7d17813..d2bc30cc3c07bf484d820844a074d67d81585f42 100644
--- a/test/core/channel/metadata_buffer_test.c
+++ b/test/core/channel/metadata_buffer_test.c
@@ -32,6 +32,7 @@
  */
 
 #include "src/core/channel/metadata_buffer.h"
+#include "src/core/support/string.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include "test/core/util/test_config.h"
@@ -42,12 +43,12 @@
 /* construct a buffer with some prefix followed by an integer converted to
    a string */
 static gpr_slice construct_buffer(size_t prefix_length, size_t index) {
-  gpr_slice buffer = gpr_slice_malloc(prefix_length + 32);
+  gpr_slice buffer = gpr_slice_malloc(prefix_length + GPR_LTOA_MIN_BUFSIZE);
   memset(GPR_SLICE_START_PTR(buffer), 'a', prefix_length);
   GPR_SLICE_SET_LENGTH(
-      buffer, prefix_length +
-                  sprintf((char *)GPR_SLICE_START_PTR(buffer) + prefix_length,
-                          "%d", (int)index));
+      buffer,
+      prefix_length +
+          gpr_ltoa(index, (char *)GPR_SLICE_START_PTR(buffer) + prefix_length));
   return buffer;
 }
 
diff --git a/test/core/end2end/cq_verifier.c b/test/core/end2end/cq_verifier.c
index 49b131c2362cae1d3b748814aa8f553490cc4a9a..287f83eebcae023998630781fbfbc619e7b982e6 100644
--- a/test/core/end2end/cq_verifier.c
+++ b/test/core/end2end/cq_verifier.c
@@ -231,100 +231,91 @@ static void verify_matches(expectation *e, grpc_event *ev) {
   }
 }
 
-static char *metadata_expectation_string(metadata *md) {
-  size_t len;
+static void metadata_expectation(gpr_strvec *buf, metadata *md) {
   size_t i;
-  char *out;
-  char *p;
-
-  if (!md) return gpr_strdup("nil");
-
-  for (len = 0, i = 0; i < md->count; i++) {
-    len += strlen(md->keys[i]);
-    len += strlen(md->values[i]);
-  }
-  len += 3 + md->count;
-
-  p = out = gpr_malloc(len);
-  *p++ = '{';
-  for (i = 0; i < md->count; i++) {
-    if (i) *p++ = ',';
-    p += sprintf(p, "%s:%s", md->keys[i], md->values[i]);
+  char *tmp;
+
+  if (!md) {
+    gpr_strvec_add(buf, gpr_strdup("nil"));
+  } else {
+    for (i = 0; i < md->count; i++) {
+      gpr_asprintf(&tmp, "%c%s:%s", i ? ',' : '{', md->keys[i], md->values[i]);
+      gpr_strvec_add(buf, tmp);
+    }
+    gpr_strvec_add(buf, gpr_strdup("}"));
   }
-  *p++ = '}';
-  *p++ = 0;
-  return out;
 }
 
-static size_t expectation_to_string(char *out, expectation *e) {
+static void expectation_to_strvec(gpr_strvec *buf, expectation *e) {
   gpr_timespec timeout;
-  char *str = NULL;
-  size_t len;
+  char *tmp;
 
   switch (e->type) {
     case GRPC_FINISH_ACCEPTED:
-      return sprintf(out, "GRPC_FINISH_ACCEPTED result=%d",
+      gpr_asprintf(&tmp, "GRPC_FINISH_ACCEPTED result=%d",
                      e->data.finish_accepted);
+      gpr_strvec_add(buf, tmp);
+      break;
     case GRPC_WRITE_ACCEPTED:
-      return sprintf(out, "GRPC_WRITE_ACCEPTED result=%d",
+      gpr_asprintf(&tmp, "GRPC_WRITE_ACCEPTED result=%d",
                      e->data.write_accepted);
+      gpr_strvec_add(buf, tmp);
+      break;
     case GRPC_INVOKE_ACCEPTED:
-      return sprintf(out, "GRPC_INVOKE_ACCEPTED");
+      gpr_strvec_add(buf, gpr_strdup("GRPC_INVOKE_ACCEPTED"));
+      break;
     case GRPC_SERVER_RPC_NEW:
       timeout = gpr_time_sub(e->data.server_rpc_new.deadline, gpr_now());
-      return sprintf(out, "GRPC_SERVER_RPC_NEW method=%s host=%s timeout=%fsec",
+      gpr_asprintf(&tmp, "GRPC_SERVER_RPC_NEW method=%s host=%s timeout=%fsec",
                      e->data.server_rpc_new.method, e->data.server_rpc_new.host,
                      timeout.tv_sec + 1e-9 * timeout.tv_nsec);
+      gpr_strvec_add(buf, tmp);
+      break;
     case GRPC_CLIENT_METADATA_READ:
-      str = metadata_expectation_string(e->data.client_metadata_read);
-      len = sprintf(out, "GRPC_CLIENT_METADATA_READ %s", str);
-      gpr_free(str);
-      return len;
+      gpr_strvec_add(buf, gpr_strdup("GRPC_CLIENT_METADATA_READ "));
+      metadata_expectation(buf, e->data.client_metadata_read);
+      break;
     case GRPC_FINISHED:
-      str = metadata_expectation_string(e->data.finished.metadata);
-      len = sprintf(out, "GRPC_FINISHED status=%d details=%s %s",
-                    e->data.finished.status, e->data.finished.details, str);
-      gpr_free(str);
-      return len;
+      gpr_asprintf(&tmp, "GRPC_FINISHED status=%d details=%s ",
+                    e->data.finished.status, e->data.finished.details);
+      gpr_strvec_add(buf, tmp);
+      metadata_expectation(buf, e->data.finished.metadata);
+      break;
     case GRPC_READ:
-      if (e->data.read) {
-        str =
-            gpr_hexdump((char *)GPR_SLICE_START_PTR(*e->data.read),
-                        GPR_SLICE_LENGTH(*e->data.read), GPR_HEXDUMP_PLAINTEXT);
-      }
-      len = sprintf(out, "GRPC_READ data=%s", str);
-      gpr_free(str);
-      return len;
+      gpr_strvec_add(buf, gpr_strdup("GRPC_READ data="));
+      gpr_strvec_add(buf, gpr_hexdump((char *)GPR_SLICE_START_PTR(*e->data.read),
+                        GPR_SLICE_LENGTH(*e->data.read), GPR_HEXDUMP_PLAINTEXT));
+      break;
     case GRPC_SERVER_SHUTDOWN:
-      return sprintf(out, "GRPC_SERVER_SHUTDOWN");
+      gpr_strvec_add(buf, gpr_strdup("GRPC_SERVER_SHUTDOWN"));
+      break;
     case GRPC_COMPLETION_DO_NOT_USE:
     case GRPC_QUEUE_SHUTDOWN:
       gpr_log(GPR_ERROR, "not implemented");
       abort();
       break;
   }
-  return 0;
 }
 
-static char *expectations_to_string(cq_verifier *v) {
-  /* allocate a large buffer: we're about to crash anyway */
-  char *buffer = gpr_malloc(32 * 1024 * 1024);
-  char *p = buffer;
+static void expectations_to_strvec(gpr_strvec *buf, cq_verifier *v) {
   expectation *e;
 
   for (e = v->expect.next; e != &v->expect; e = e->next) {
-    p += expectation_to_string(p, e);
-    *p++ = '\n';
+    expectation_to_strvec(buf, e);
+    gpr_strvec_add(buf, gpr_strdup("\n"));
   }
-
-  *p = 0;
-  return buffer;
 }
 
 static void fail_no_event_received(cq_verifier *v) {
-  char *expectations = expectations_to_string(v);
-  gpr_log(GPR_ERROR, "no event received, but expected:\n%s", expectations);
-  gpr_free(expectations);
+  gpr_strvec buf;
+  char *msg;
+  gpr_strvec_init(&buf);
+  gpr_strvec_add(&buf, gpr_strdup("no event received, but expected:\n"));
+  expectations_to_strvec(&buf, v);
+  msg = gpr_strvec_flatten(&buf, NULL);
+  gpr_log(GPR_ERROR, "%s", msg);
+  gpr_strvec_destroy(&buf);
+  gpr_free(msg);
   abort();
 }
 
@@ -333,9 +324,10 @@ void cq_verify(cq_verifier *v) {
       gpr_time_add(gpr_now(), gpr_time_from_micros(10 * GPR_US_PER_SEC));
   grpc_event *ev;
   expectation *e;
+  char *s;
+  gpr_strvec have_tags;
 
-  char have_tags[512] = {0};
-  char *phave = have_tags;
+  gpr_strvec_init(&have_tags);
 
   while (v->expect.next != &v->expect) {
     ev = grpc_completion_queue_next(v->cq, deadline);
@@ -344,7 +336,8 @@ void cq_verify(cq_verifier *v) {
     }
 
     for (e = v->expect.next; e != &v->expect; e = e->next) {
-      phave += sprintf(phave, " %p", e->tag);
+      gpr_asprintf(&s, " %p", e->tag);
+      gpr_strvec_add(&have_tags, s);
       if (e->tag == ev->tag) {
         verify_matches(e, ev);
         e->next->prev = e->prev;
@@ -354,15 +347,20 @@ void cq_verify(cq_verifier *v) {
       }
     }
     if (e == &v->expect) {
-      char *s = grpc_event_string(ev);
+      s = grpc_event_string(ev);
       gpr_log(GPR_ERROR, "event not found: %s", s);
-      gpr_log(GPR_ERROR, "have tags:%s", have_tags);
       gpr_free(s);
+      s = gpr_strvec_flatten(&have_tags, NULL);
+      gpr_log(GPR_ERROR, "have tags:%s", s);
+      gpr_free(s);
+      gpr_strvec_destroy(&have_tags);
       abort();
     }
 
     grpc_event_finish(ev);
   }
+
+  gpr_strvec_destroy(&have_tags);
 }
 
 void cq_verify_empty(cq_verifier *v) {
diff --git a/test/core/end2end/tests/census_simple_request.c b/test/core/end2end/tests/census_simple_request.c
index 719f0fe6623bff17bde0fca45364aea4f8cb1248..86cef437be8d875d4598be63ae489aebb0bc37ef 100644
--- a/test/core/end2end/tests/census_simple_request.c
+++ b/test/core/end2end/tests/census_simple_request.c
@@ -37,6 +37,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "src/core/support/string.h"
 #include <grpc/byte_buffer.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
@@ -145,7 +146,7 @@ static void test_body(grpc_end2end_test_fixture f) {
 static void test_invoke_request_with_census(
     grpc_end2end_test_config config, const char *name,
     void (*body)(grpc_end2end_test_fixture f)) {
-  char fullname[64];
+  char *fullname;
   grpc_end2end_test_fixture f;
   grpc_arg client_arg, server_arg;
   grpc_channel_args client_args, server_args;
@@ -163,11 +164,12 @@ static void test_invoke_request_with_census(
   server_args.num_args = 1;
   server_args.args = &server_arg;
 
-  sprintf(fullname, "%s/%s", __FUNCTION__, name);
+  gpr_asprintf(&fullname, "%s/%s", __FUNCTION__, name);
   f = begin_test(config, fullname, &client_args, &server_args);
   body(f);
   end_test(&f);
   config.tear_down_data(&f);
+  gpr_free(fullname);
 }
 
 void grpc_end2end_tests(grpc_end2end_test_config config) {
diff --git a/test/core/end2end/tests/simple_request.c b/test/core/end2end/tests/simple_request.c
index 23fc201d8435c5c40467cc9b09eb68a423af4f59..f8894a8ba923e45c7c3c3229b8d78d135f43c4b8 100644
--- a/test/core/end2end/tests/simple_request.c
+++ b/test/core/end2end/tests/simple_request.c
@@ -37,6 +37,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "src/core/support/string.h"
 #include <grpc/byte_buffer.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
@@ -198,15 +199,16 @@ static void simple_request_body2(grpc_end2end_test_fixture f) {
 static void test_invoke_simple_request(
     grpc_end2end_test_config config, const char *name,
     void (*body)(grpc_end2end_test_fixture f)) {
-  char fullname[64];
+  char *fullname;
   grpc_end2end_test_fixture f;
 
-  sprintf(fullname, "%s/%s", __FUNCTION__, name);
+  gpr_asprintf(&fullname, "%s/%s", __FUNCTION__, name);
 
   f = begin_test(config, fullname, NULL, NULL);
   body(f);
   end_test(&f);
   config.tear_down_data(&f);
+  gpr_free(fullname);
 }
 
 static void test_invoke_10_simple_requests(grpc_end2end_test_config config) {
diff --git a/test/core/security/credentials_test.c b/test/core/security/credentials_test.c
index ec21e0d42f71efd679a63340dcda67fefb919043..1c83cc8059024d47811d363f5624056deae8dabc 100644
--- a/test/core/security/credentials_test.c
+++ b/test/core/security/credentials_test.c
@@ -498,10 +498,8 @@ static void validate_service_account_http_request(
   char *expected_body = NULL;
   GPR_ASSERT(body != NULL);
   GPR_ASSERT(body_size != 0);
-  expected_body = gpr_malloc(strlen(expected_service_account_http_body_prefix) +
-                             strlen(test_signed_jwt) + 1);
-  sprintf(expected_body, "%s%s", expected_service_account_http_body_prefix,
-          test_signed_jwt);
+  gpr_asprintf(&expected_body, "%s%s",
+               expected_service_account_http_body_prefix, test_signed_jwt);
   GPR_ASSERT(strlen(expected_body) == body_size);
   GPR_ASSERT(!memcmp(expected_body, body, body_size));
   gpr_free(expected_body);
diff --git a/test/core/statistics/hash_table_test.c b/test/core/statistics/hash_table_test.c
index ebfc2a2a9a00b08fbe4e6c9621ad70a8c911f007..f8df2574a423d9e7068a211b61c00f3bc3a636fe 100644
--- a/test/core/statistics/hash_table_test.c
+++ b/test/core/statistics/hash_table_test.c
@@ -38,6 +38,7 @@
 #include "src/core/statistics/hash_table.h"
 
 #include "src/core/support/murmur_hash.h"
+#include "src/core/support/string.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
@@ -187,15 +188,15 @@ static void test_insertion_and_deletion_with_high_collision_rate(void) {
   census_ht_option opt = {CENSUS_HT_POINTER, 13,   &force_collision,
                           &cmp_str_keys,     NULL, NULL};
   census_ht* ht = census_ht_create(&opt);
-  char key_str[1000][10];
+  char key_str[1000][GPR_LTOA_MIN_BUFSIZE];
   gpr_uint64 val = 0;
   int i = 0;
   for (i = 0; i < 1000; i++) {
     census_ht_key key;
     key.ptr = key_str[i];
-    sprintf(key_str[i], "%d", i);
+    gpr_ltoa(i, key_str[i]);
     census_ht_insert(ht, key, (void*)(&val));
-    printf("%d\n", i);
+    gpr_log(GPR_INFO, "%d\n", i);
     GPR_ASSERT(census_ht_get_size(ht) == (i + 1));
   }
   for (i = 0; i < 1000; i++) {
diff --git a/test/core/transport/chttp2/hpack_table_test.c b/test/core/transport/chttp2/hpack_table_test.c
index 1576a30c1b0b59811b59fa0557e48f204e6c23de..d155dee9dc2f4def53809a5e6161692a13bdac6c 100644
--- a/test/core/transport/chttp2/hpack_table_test.c
+++ b/test/core/transport/chttp2/hpack_table_test.c
@@ -36,6 +36,7 @@
 #include <string.h>
 #include <stdio.h>
 
+#include "src/core/support/string.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include "test/core/util/test_config.h"
@@ -131,8 +132,8 @@ static void test_static_lookup(void) {
 static void test_many_additions(void) {
   grpc_chttp2_hptbl tbl;
   int i;
-  char key[32];
-  char value[32];
+  char *key;
+  char *value;
   grpc_mdctx *mdctx;
 
   LOG_TEST();
@@ -141,14 +142,18 @@ static void test_many_additions(void) {
   grpc_chttp2_hptbl_init(&tbl, mdctx);
 
   for (i = 0; i < 1000000; i++) {
-    sprintf(key, "K:%d", i);
-    sprintf(value, "VALUE:%d", i);
+    gpr_asprintf(&key, "K:%d", i);
+    gpr_asprintf(&value, "VALUE:%d", i);
     grpc_chttp2_hptbl_add(&tbl, grpc_mdelem_from_strings(mdctx, key, value));
     assert_index(&tbl, 1 + GRPC_CHTTP2_LAST_STATIC_ENTRY, key, value);
+    gpr_free(key);
+    gpr_free(value);
     if (i) {
-      sprintf(key, "K:%d", i - 1);
-      sprintf(value, "VALUE:%d", i - 1);
+      gpr_asprintf(&key, "K:%d", i - 1);
+      gpr_asprintf(&value, "VALUE:%d", i - 1);
       assert_index(&tbl, 2 + GRPC_CHTTP2_LAST_STATIC_ENTRY, key, value);
+      gpr_free(key);
+      gpr_free(value);
     }
   }
 
@@ -226,7 +231,7 @@ static void test_find(void) {
 
   /* overflow the string buffer, check find still works */
   for (i = 0; i < 10000; i++) {
-    sprintf(buffer, "%d", i);
+    gpr_ltoa(i, buffer);
     grpc_chttp2_hptbl_add(&tbl,
                           grpc_mdelem_from_strings(mdctx, "test", buffer));
   }
@@ -245,7 +250,7 @@ static void test_find(void) {
 
   for (i = 0; i < tbl.num_ents; i++) {
     int expect = 9999 - i;
-    sprintf(buffer, "%d", expect);
+    gpr_ltoa(expect, buffer);
 
     r = find_simple(&tbl, "test", buffer);
     GPR_ASSERT(r.index == i + 1 + GRPC_CHTTP2_LAST_STATIC_ENTRY);
diff --git a/test/core/transport/chttp2/stream_encoder_test.c b/test/core/transport/chttp2/stream_encoder_test.c
index eb0f688f58e9f02395c5e2562001b6790dfeef40..5e8ec0a1af85135dbb3d34541a31167b97c6a913 100644
--- a/test/core/transport/chttp2/stream_encoder_test.c
+++ b/test/core/transport/chttp2/stream_encoder_test.c
@@ -186,7 +186,7 @@ static void encode_int_to_str(int i, char *p) {
 static void test_decode_table_overflow(void) {
   int i;
   char key[3], value[3];
-  char expect[128];
+  char *expect;
 
   for (i = 0; i < 114; i++) {
     if (i > 0) {
@@ -197,18 +197,21 @@ static void test_decode_table_overflow(void) {
     encode_int_to_str(i + 1, value);
 
     if (i + 61 >= 127) {
-      sprintf(expect, "000009 0104 deadbeef ff%02x 40 02%02x%02x 02%02x%02x",
-              i + 61 - 127, key[0], key[1], value[0], value[1]);
+      gpr_asprintf(&expect,
+                   "000009 0104 deadbeef ff%02x 40 02%02x%02x 02%02x%02x",
+                   i + 61 - 127, key[0], key[1], value[0], value[1]);
     } else if (i > 0) {
-      sprintf(expect, "000008 0104 deadbeef %02x 40 02%02x%02x 02%02x%02x",
-              0x80 + 61 + i, key[0], key[1], value[0], value[1]);
+      gpr_asprintf(&expect,
+                   "000008 0104 deadbeef %02x 40 02%02x%02x 02%02x%02x",
+                   0x80 + 61 + i, key[0], key[1], value[0], value[1]);
     } else {
-      sprintf(expect, "000007 0104 deadbeef 40 02%02x%02x 02%02x%02x", key[0],
-              key[1], value[0], value[1]);
+      gpr_asprintf(&expect, "000007 0104 deadbeef 40 02%02x%02x 02%02x%02x",
+                   key[0], key[1], value[0], value[1]);
     }
 
     add_sopb_header(key, value);
     verify_sopb(0, 0, 0, expect);
+    gpr_free(expect);
   }
 
   /* if the above passes, then we must have just knocked this pair out of the
diff --git a/test/core/transport/chttp2/timeout_encoding_test.c b/test/core/transport/chttp2/timeout_encoding_test.c
index ffa0070e341e48ab0fa4b75318a5bbc69a990f88..0ad90dbcef8f68fcc45af27a3d6fa41ff626e44c 100644
--- a/test/core/transport/chttp2/timeout_encoding_test.c
+++ b/test/core/transport/chttp2/timeout_encoding_test.c
@@ -36,6 +36,8 @@
 #include <stdio.h>
 #include <string.h>
 
+#include "src/core/support/string.h"
+#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/useful.h>
 #include "test/core/util/test_config.h"
@@ -93,16 +95,23 @@ void decode_suite(char ext, gpr_timespec (*answer)(long x)) {
                       1234567, 12345678, 123456789, 98765432, 9876543, 987654,
                       98765,   9876,     987,       98,       9};
   int i;
-  char input[32];
+  char *input;
   for (i = 0; i < GPR_ARRAY_SIZE(test_vals); i++) {
-    sprintf(input, "%ld%c", test_vals[i], ext);
+    gpr_asprintf(&input, "%ld%c", test_vals[i], ext);
     assert_decodes_as(input, answer(test_vals[i]));
-    sprintf(input, "   %ld%c", test_vals[i], ext);
+    gpr_free(input);
+
+    gpr_asprintf(&input, "   %ld%c", test_vals[i], ext);
     assert_decodes_as(input, answer(test_vals[i]));
-    sprintf(input, "%ld %c", test_vals[i], ext);
+    gpr_free(input);
+
+    gpr_asprintf(&input, "%ld %c", test_vals[i], ext);
     assert_decodes_as(input, answer(test_vals[i]));
-    sprintf(input, "%ld %c  ", test_vals[i], ext);
+    gpr_free(input);
+
+    gpr_asprintf(&input, "%ld %c  ", test_vals[i], ext);
     assert_decodes_as(input, answer(test_vals[i]));
+    gpr_free(input);
   }
 }
 
diff --git a/test/core/transport/metadata_test.c b/test/core/transport/metadata_test.c
index 804096d0e1e078c6251a38336e3bd15e4d3f9a62..b23db894bee185c8bfcda605b5067869001046dc 100644
--- a/test/core/transport/metadata_test.c
+++ b/test/core/transport/metadata_test.c
@@ -35,6 +35,7 @@
 
 #include <stdio.h>
 
+#include "src/core/support/string.h"
 #include "src/core/transport/chttp2/bin_encoder.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
@@ -99,7 +100,7 @@ static void test_create_metadata(void) {
 
 static void test_create_many_ephemeral_metadata(void) {
   grpc_mdctx *ctx;
-  char buffer[256];
+  char buffer[GPR_LTOA_MIN_BUFSIZE];
   long i;
   size_t mdtab_capacity_before;
 
@@ -109,7 +110,7 @@ static void test_create_many_ephemeral_metadata(void) {
   mdtab_capacity_before = grpc_mdctx_get_mdtab_capacity_test_only(ctx);
   /* add, and immediately delete a bunch of different elements */
   for (i = 0; i < MANY; i++) {
-    sprintf(buffer, "%ld", i);
+    gpr_ltoa(i, buffer);
     grpc_mdelem_unref(grpc_mdelem_from_strings(ctx, "a", buffer));
   }
   /* capacity should not grow */
@@ -120,7 +121,7 @@ static void test_create_many_ephemeral_metadata(void) {
 
 static void test_create_many_persistant_metadata(void) {
   grpc_mdctx *ctx;
-  char buffer[256];
+  char buffer[GPR_LTOA_MIN_BUFSIZE];
   long i;
   grpc_mdelem **created = gpr_malloc(sizeof(grpc_mdelem *) * MANY);
   grpc_mdelem *md;
@@ -130,12 +131,12 @@ static void test_create_many_persistant_metadata(void) {
   ctx = grpc_mdctx_create();
   /* add phase */
   for (i = 0; i < MANY; i++) {
-    sprintf(buffer, "%ld", i);
+    gpr_ltoa(i, buffer);
     created[i] = grpc_mdelem_from_strings(ctx, "a", buffer);
   }
   /* verify phase */
   for (i = 0; i < MANY; i++) {
-    sprintf(buffer, "%ld", i);
+    gpr_ltoa(i, buffer);
     md = grpc_mdelem_from_strings(ctx, "a", buffer);
     GPR_ASSERT(md == created[i]);
     grpc_mdelem_unref(md);
@@ -176,7 +177,7 @@ static void test_spin_creating_the_same_thing(void) {
 static void test_things_stick_around(void) {
   grpc_mdctx *ctx;
   int i, j;
-  char buffer[64];
+  char *buffer;
   int nstrs = 10000;
   grpc_mdstr **strs = gpr_malloc(sizeof(grpc_mdstr *) * nstrs);
   int *shuf = gpr_malloc(sizeof(int) * nstrs);
@@ -187,9 +188,10 @@ static void test_things_stick_around(void) {
   ctx = grpc_mdctx_create();
 
   for (i = 0; i < nstrs; i++) {
-    sprintf(buffer, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%dx", i);
+    gpr_asprintf(&buffer, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%dx", i);
     strs[i] = grpc_mdstr_from_string(ctx, buffer);
     shuf[i] = i;
+    gpr_free(buffer);
   }
 
   for (i = 0; i < nstrs; i++) {
@@ -208,10 +210,11 @@ static void test_things_stick_around(void) {
   for (i = 0; i < nstrs; i++) {
     grpc_mdstr_unref(strs[shuf[i]]);
     for (j = i + 1; j < nstrs; j++) {
-      sprintf(buffer, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%dx", shuf[j]);
+      gpr_asprintf(&buffer, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%dx", shuf[j]);
       test = grpc_mdstr_from_string(ctx, buffer);
       GPR_ASSERT(test == strs[shuf[j]]);
       grpc_mdstr_unref(test);
+      gpr_free(buffer);
     }
   }
 
diff --git a/tools/clang-format/clang-format-all.sh b/tools/clang-format/clang-format-all.sh
deleted file mode 100755
index 62228b0b015138724791b94f4f495aa41ba10b55..0000000000000000000000000000000000000000
--- a/tools/clang-format/clang-format-all.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-set -e
-source $(dirname $0)/config.sh
-cd $(dirname $0)/../..
-for dir in src test include
-do
-  find $dir -name '*.c' -or -name '*.cc' -or -name '*.h' | xargs $CLANG_FORMAT -i
-done
-
diff --git a/tools/clang-format/config.sh b/tools/clang-format/config.sh
deleted file mode 100644
index 3adf2678e7a422e5e32edb67997855d231822e5f..0000000000000000000000000000000000000000
--- a/tools/clang-format/config.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-CLANG_FORMAT=clang-format-3.5
-
-set -ex
-
-if not hash $CLANG_FORMAT 2>/dev/null; then
-  echo "$CLANG_FORMAT is needed but not installed"
-  echo "perhaps try:"
-  echo "  sudo apt-get install $CLANG_FORMAT"
-  exit 1
-fi
-
diff --git a/tools/dockerfile/grpc_java/Dockerfile b/tools/dockerfile/grpc_java/Dockerfile
index 78659dedeb22cbc877dec1be1a04fac31d6d5819..f234f514e6f76d5fa0791b6c6f23d96a0eff7e86 100644
--- a/tools/dockerfile/grpc_java/Dockerfile
+++ b/tools/dockerfile/grpc_java/Dockerfile
@@ -1,9 +1,6 @@
 # Dockerfile for the gRPC Java dev image
 FROM grpc/java_base
 
-# Start the daemon that allows access to private git-on-borg repos
-RUN /var/local/git/gcompute-tools/git-cookie-authdaemon
-
 RUN  cd /var/local/git/grpc-java/lib/okhttp && \
   mvn -pl okhttp -am install
 RUN  cd /var/local/git/grpc-java/lib/netty && \
@@ -13,4 +10,4 @@ RUN cd /var/local/git/grpc-java && \
   mvn install
 
 # Specify the default command such that the interop server runs on its known testing port
-CMD ["/var/local/git/grpc-java/run-test-server.sh", "--transport=HTTP2_NETTY_TLS", "--grpc_version=2", "--port=8030"]
+CMD ["/var/local/git/grpc-java/run-test-server.sh", "--use_tls=true", "--port=8030"]
diff --git a/tools/dockerfile/grpc_java_base/Dockerfile b/tools/dockerfile/grpc_java_base/Dockerfile
index 44fa52c0e8bf42e3b5954c56b2ce71e319fbf541..3271d1b2c2dcd22dd835a32516d76c72d3f5fc14 100644
--- a/tools/dockerfile/grpc_java_base/Dockerfile
+++ b/tools/dockerfile/grpc_java_base/Dockerfile
@@ -20,14 +20,24 @@ ENV M2_HOME /var/local/apache-maven-3.2.1
 ENV PATH $PATH:$JAVA_HOME/bin:$M2_HOME/bin
 ENV LD_LIBRARY_PATH /usr/local/lib
 
-# Start the daemon that allows access to the protected git-on-borg repos
-RUN /var/local/git/gcompute-tools/git-cookie-authdaemon
+# Install a GitHub SSH service credential that gives access to the GitHub repo while it's private
+# TODO: remove this once the repo is public
+ADD .ssh .ssh
+RUN chmod 600 .ssh/github.rsa
+RUN mkdir -p $HOME/.ssh && echo 'Host github.com' > $HOME/.ssh/config
+RUN echo "    IdentityFile /.ssh/github.rsa" >> $HOME/.ssh/config
+RUN echo 'StrictHostKeyChecking no' >> $HOME/.ssh/config
 
-RUN git clone --recursive https://team.googlesource.com/one-platform-grpc-team/grpc-java /var/local/git/grpc-java
+# Get the protobuf source from GitHub and install it
+RUN git clone --recursive --branch v2.6.1 git@github.com:google/protobuf.git /var/local/git/protobuf
+RUN cd /var/local/git/protobuf && \
+  ./autogen.sh && \
+  ./configure --prefix=/usr && \
+  make -j12 && make check && make install && make clean
 
 RUN cd /var/local/git/grpc-java/lib/okhttp && \
   mvn -pl okhttp -am validate
 RUN cd /var/local/git/grpc-java/lib/netty && \
   mvn -pl codec-http2 -am validate
 RUN cd /var/local/git/grpc-java && \
-  mvn validate
\ No newline at end of file
+  mvn validate
diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh
index 145685305c35cc451da30027a30668006a8199d8..d97f82943599a10c03ff0b8d1d8b2799db1cf36a 100755
--- a/tools/gce_setup/grpc_docker.sh
+++ b/tools/gce_setup/grpc_docker.sh
@@ -655,7 +655,7 @@ grpc_interop_gen_go_cmd() {
 grpc_interop_gen_java_cmd() {
     local cmd_prefix="sudo docker run grpc/java";
     local test_script="/var/local/git/grpc-java/run-test-client.sh";
-    local test_script+=" --transport=NETTY_TLS --grpc_version=2"
+    local test_script+=" --server_host_override=foo.test.google.com --use_test_ca=true --use_tls=true"
     local the_cmd="$cmd_prefix $test_script $@";
     echo $the_cmd
 }
@@ -683,7 +683,7 @@ grpc_interop_gen_php_cmd() {
 #   flags= .... # generic flags to include the command
 #   cmd=$($grpc_gen_test_cmd $flags)
 grpc_interop_gen_cxx_cmd() {
-    local cmd_prefix="sudo docker run grpc/cxx"; 
+    local cmd_prefix="sudo docker run grpc/cxx";
     local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl";
     local the_cmd="$cmd_prefix $test_script $@";
     echo $the_cmd
diff --git a/tools/run_tests/build_node.sh b/tools/run_tests/build_node.sh
index 600b1bde8cdc491a72f6a97b155aa66e0316e055..4b092982b29a3f9dc29895e28cf9b43d186057ea 100755
--- a/tools/run_tests/build_node.sh
+++ b/tools/run_tests/build_node.sh
@@ -2,19 +2,18 @@
 
 set -ex
 
+CONFIG=${CONFIG:-opt}
+
 # change to grpc repo root
 cd $(dirname $0)/../..
 
 # tells npm install to look for files in that directory
 export GRPC_ROOT=`pwd`
 # tells npm install the subdirectory with library files
-export GRPC_LIB_SUBDIR=libs/opt
+export GRPC_LIB_SUBDIR=libs/$CONFIG
 # tells npm install not to use default locations
 export GRPC_NO_INSTALL=yes
 
-# build the c libraries
-make -j static_c
-
 cd src/node
 
 npm install
diff --git a/tools/run_tests/build_php.sh b/tools/run_tests/build_php.sh
index 6841656bdb8d3734c2724438aaf5606f5ef0ce2e..0a8d0c74923868318403d225be06b5cbf56fbf4b 100755
--- a/tools/run_tests/build_php.sh
+++ b/tools/run_tests/build_php.sh
@@ -2,14 +2,13 @@
 
 set -ex
 
+CONFIG=${CONFIG:-opt}
+
 # change to grpc repo root
 cd $(dirname $0)/../..
 
 root=`pwd`
-export GRPC_LIB_SUBDIR=libs/opt
-
-# make the libraries
-make -j static_c
+export GRPC_LIB_SUBDIR=libs/$CONFIG
 
 # build php
 cd src/php
@@ -18,4 +17,3 @@ cd ext/grpc
 phpize
 ./configure --enable-grpc=$root
 make
-
diff --git a/tools/run_tests/run_node.sh b/tools/run_tests/run_node.sh
new file mode 100755
index 0000000000000000000000000000000000000000..005629594908cb014edab8f8317726d9c3cc3b21
--- /dev/null
+++ b/tools/run_tests/run_node.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -ex
+
+# change to grpc repo root
+cd $(dirname $0)/../..
+
+root=`pwd`
+
+$root/src/node/node_modules/mocha/bin/mocha $root/src/node/test
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index da849f04cb0f5c0dfaaa81c58ad975bfed3ff1fb..b7248e524b4c9bd0e96f566caaee29c1af55d39f 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -46,8 +46,8 @@ class CLanguage(object):
     self.make_target = make_target
     with open('tools/run_tests/tests.json') as f:
       js = json.load(f)
-      self.binaries = [tgt['name'] 
-                       for tgt in js 
+      self.binaries = [tgt['name']
+                       for tgt in js
                        if tgt['language'] == test_lang]
 
   def test_binaries(self, config):
@@ -59,6 +59,19 @@ class CLanguage(object):
   def build_steps(self):
     return []
 
+class NodeLanguage(object):
+
+  def __init__(self):
+    self.allow_hashing = False
+
+  def test_binaries(self, config):
+    return ['tools/run_tests/run_node.sh']
+
+  def make_targets(self):
+    return ['static_c']
+
+  def build_steps(self):
+    return [['tools/run_tests/build_node.sh']]
 
 class PhpLanguage(object):
 
@@ -69,7 +82,7 @@ class PhpLanguage(object):
     return ['src/php/bin/run_tests.sh']
 
   def make_targets(self):
-    return []
+    return ['static_c']
 
   def build_steps(self):
     return [['tools/run_tests/build_php.sh']]
@@ -107,6 +120,7 @@ _DEFAULT = ['dbg', 'opt']
 _LANGUAGES = {
     'c++': CLanguage('cxx', 'c++'),
     'c': CLanguage('c', 'c'),
+    'node': NodeLanguage(),
     'php': PhpLanguage(),
     'python': PythonLanguage(),
 }
@@ -190,8 +204,8 @@ class TestCache(object):
 
 def _build_and_run(check_cancelled, newline_on_success, cache):
   """Do one pass of building & running tests."""
-  # build latest, sharing cpu between the various makes
-  if not jobset.run(build_steps):
+  # build latest sequentially
+  if not jobset.run(build_steps, maxjobs=1):
     return 1
 
   # run all the tests