diff --git a/.travis.yml b/.travis.yml
index e43a89e453a7fd0831c8aed07ffbce96dfd726e2..165f8923c0e74073a7fd35f2fcb65482ab8b36f3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,9 +19,12 @@ env:
     - CONFIG=opt TEST=python
     - CONFIG=gcov TEST=c
     - CONFIG=gcov TEST=c++
+    - USE_GCC=4.4 CONFIG=opt TEST=build
+    - USE_GCC=4.5 CONFIG=opt TEST=build
 script:
   - rvm use $RUBY_VERSION
   - gem install bundler
+  - if [ ! -z "$USE_GCC" ] ; then export CC=gcc-$USE_GCC ; export CXX=g++-$USE_GCC ; fi
   - ./tools/run_tests/run_tests.py -l $TEST -t -j 16 -c $CONFIG -s 4.0
 after_success:
   - if [ "$CONFIG" = "gcov" ] ; then coveralls --exclude third_party --exclude gens -b. --gcov-options '\-p' ; fi
diff --git a/Makefile b/Makefile
index 0742994982e09a94e6eafc9d5c63f4c2352f4ed2..0b2fe0701d1d8126587b0a05702f4187f96723ff 100644
--- a/Makefile
+++ b/Makefile
@@ -616,10 +616,12 @@ transport_metadata_test: $(BINDIR)/$(CONFIG)/transport_metadata_test
 transport_security_test: $(BINDIR)/$(CONFIG)/transport_security_test
 async_end2end_test: $(BINDIR)/$(CONFIG)/async_end2end_test
 channel_arguments_test: $(BINDIR)/$(CONFIG)/channel_arguments_test
+cli_call_test: $(BINDIR)/$(CONFIG)/cli_call_test
 credentials_test: $(BINDIR)/$(CONFIG)/credentials_test
 cxx_time_test: $(BINDIR)/$(CONFIG)/cxx_time_test
 end2end_test: $(BINDIR)/$(CONFIG)/end2end_test
 generic_end2end_test: $(BINDIR)/$(CONFIG)/generic_end2end_test
+grpc_cli: $(BINDIR)/$(CONFIG)/grpc_cli
 grpc_cpp_plugin: $(BINDIR)/$(CONFIG)/grpc_cpp_plugin
 grpc_python_plugin: $(BINDIR)/$(CONFIG)/grpc_python_plugin
 grpc_ruby_plugin: $(BINDIR)/$(CONFIG)/grpc_ruby_plugin
@@ -1070,7 +1072,7 @@ buildtests: buildtests_c buildtests_cxx
 
 buildtests_c: privatelibs_c $(BINDIR)/$(CONFIG)/alarm_heap_test $(BINDIR)/$(CONFIG)/alarm_list_test $(BINDIR)/$(CONFIG)/alarm_test $(BINDIR)/$(CONFIG)/alpn_test $(BINDIR)/$(CONFIG)/bin_encoder_test $(BINDIR)/$(CONFIG)/census_hash_table_test $(BINDIR)/$(CONFIG)/census_statistics_multiple_writers_circular_buffer_test $(BINDIR)/$(CONFIG)/census_statistics_multiple_writers_test $(BINDIR)/$(CONFIG)/census_statistics_performance_test $(BINDIR)/$(CONFIG)/census_statistics_quick_test $(BINDIR)/$(CONFIG)/census_statistics_small_log_test $(BINDIR)/$(CONFIG)/census_stub_test $(BINDIR)/$(CONFIG)/census_window_stats_test $(BINDIR)/$(CONFIG)/chttp2_status_conversion_test $(BINDIR)/$(CONFIG)/chttp2_stream_encoder_test $(BINDIR)/$(CONFIG)/chttp2_stream_map_test $(BINDIR)/$(CONFIG)/chttp2_transport_end2end_test $(BINDIR)/$(CONFIG)/dualstack_socket_test $(BINDIR)/$(CONFIG)/echo_client $(BINDIR)/$(CONFIG)/echo_server $(BINDIR)/$(CONFIG)/echo_test $(BINDIR)/$(CONFIG)/fd_posix_test $(BINDIR)/$(CONFIG)/fling_client $(BINDIR)/$(CONFIG)/fling_server $(BINDIR)/$(CONFIG)/fling_stream_test $(BINDIR)/$(CONFIG)/fling_test $(BINDIR)/$(CONFIG)/gpr_cancellable_test $(BINDIR)/$(CONFIG)/gpr_cmdline_test $(BINDIR)/$(CONFIG)/gpr_env_test $(BINDIR)/$(CONFIG)/gpr_file_test $(BINDIR)/$(CONFIG)/gpr_histogram_test $(BINDIR)/$(CONFIG)/gpr_host_port_test $(BINDIR)/$(CONFIG)/gpr_log_test $(BINDIR)/$(CONFIG)/gpr_slice_buffer_test $(BINDIR)/$(CONFIG)/gpr_slice_test $(BINDIR)/$(CONFIG)/gpr_string_test $(BINDIR)/$(CONFIG)/gpr_sync_test $(BINDIR)/$(CONFIG)/gpr_thd_test $(BINDIR)/$(CONFIG)/gpr_time_test $(BINDIR)/$(CONFIG)/gpr_useful_test $(BINDIR)/$(CONFIG)/grpc_base64_test $(BINDIR)/$(CONFIG)/grpc_byte_buffer_reader_test $(BINDIR)/$(CONFIG)/grpc_channel_stack_test $(BINDIR)/$(CONFIG)/grpc_completion_queue_test $(BINDIR)/$(CONFIG)/grpc_credentials_test $(BINDIR)/$(CONFIG)/grpc_json_token_test $(BINDIR)/$(CONFIG)/grpc_stream_op_test $(BINDIR)/$(CONFIG)/hpack_parser_test $(BINDIR)/$(CONFIG)/hpack_table_test $(BINDIR)/$(CONFIG)/httpcli_format_request_test $(BINDIR)/$(CONFIG)/httpcli_parser_test $(BINDIR)/$(CONFIG)/httpcli_test $(BINDIR)/$(CONFIG)/json_rewrite $(BINDIR)/$(CONFIG)/json_rewrite_test $(BINDIR)/$(CONFIG)/json_test $(BINDIR)/$(CONFIG)/lame_client_test $(BINDIR)/$(CONFIG)/message_compress_test $(BINDIR)/$(CONFIG)/metadata_buffer_test $(BINDIR)/$(CONFIG)/multi_init_test $(BINDIR)/$(CONFIG)/murmur_hash_test $(BINDIR)/$(CONFIG)/no_server_test $(BINDIR)/$(CONFIG)/poll_kick_posix_test $(BINDIR)/$(CONFIG)/resolve_address_test $(BINDIR)/$(CONFIG)/secure_endpoint_test $(BINDIR)/$(CONFIG)/sockaddr_utils_test $(BINDIR)/$(CONFIG)/tcp_client_posix_test $(BINDIR)/$(CONFIG)/tcp_posix_test $(BINDIR)/$(CONFIG)/tcp_server_posix_test $(BINDIR)/$(CONFIG)/time_averaged_stats_test $(BINDIR)/$(CONFIG)/time_test $(BINDIR)/$(CONFIG)/timeout_encoding_test $(BINDIR)/$(CONFIG)/transport_metadata_test $(BINDIR)/$(CONFIG)/transport_security_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_bad_hostname_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_after_accept_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_after_accept_and_writes_closed_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_after_invoke_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_before_invoke_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_in_a_vacuum_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_census_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_disappearing_server_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_inflight_calls_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_tags_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_empty_batch_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_graceful_server_shutdown_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_invoke_large_request_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_max_concurrent_streams_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_no_op_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_ping_pong_streaming_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_response_with_binary_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_response_with_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_response_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_with_large_metadata_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_simple_delayed_request_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_thread_stress_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_writes_done_hangs_with_pending_read_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_after_accept_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_after_accept_and_writes_closed_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_after_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_before_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_cancel_in_a_vacuum_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_census_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_disappearing_server_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_inflight_calls_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_early_server_shutdown_finishes_tags_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_graceful_server_shutdown_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_invoke_large_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_max_concurrent_streams_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_no_op_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_ping_pong_streaming_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_response_with_binary_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_response_with_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_response_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_response_with_trailing_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_with_large_metadata_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_request_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_simple_delayed_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_thread_stress_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fake_security_writes_done_hangs_with_pending_read_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_bad_hostname_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_after_accept_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_after_accept_and_writes_closed_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_after_invoke_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_before_invoke_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_in_a_vacuum_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_census_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_disappearing_server_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_inflight_calls_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_tags_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_empty_batch_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_graceful_server_shutdown_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_invoke_large_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_max_concurrent_streams_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_no_op_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_ping_pong_streaming_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_response_with_binary_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_response_with_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_response_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_with_large_metadata_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_simple_delayed_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_thread_stress_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_writes_done_hangs_with_pending_read_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_after_accept_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_after_accept_and_writes_closed_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_after_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_before_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_cancel_in_a_vacuum_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_census_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_disappearing_server_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_inflight_calls_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_early_server_shutdown_finishes_tags_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_graceful_server_shutdown_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_invoke_large_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_max_concurrent_streams_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_no_op_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_ping_pong_streaming_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_response_with_binary_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_response_with_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_response_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_response_with_trailing_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_with_large_metadata_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_request_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_simple_delayed_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_thread_stress_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_writes_done_hangs_with_pending_read_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_bad_hostname_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_after_accept_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_after_accept_and_writes_closed_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_after_invoke_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_before_invoke_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_in_a_vacuum_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_census_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_disappearing_server_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_early_server_shutdown_finishes_inflight_calls_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_early_server_shutdown_finishes_tags_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_empty_batch_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_graceful_server_shutdown_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_invoke_large_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_max_concurrent_streams_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_no_op_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_ping_pong_streaming_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_response_with_binary_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_response_with_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_response_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_with_large_metadata_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_simple_delayed_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_thread_stress_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_writes_done_hangs_with_pending_read_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_after_accept_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_after_accept_and_writes_closed_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_after_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_before_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_cancel_in_a_vacuum_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_census_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_disappearing_server_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_early_server_shutdown_finishes_inflight_calls_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_early_server_shutdown_finishes_tags_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_graceful_server_shutdown_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_invoke_large_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_max_concurrent_streams_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_no_op_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_ping_pong_streaming_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_response_with_binary_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_response_with_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_response_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_response_with_trailing_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_with_large_metadata_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_request_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_simple_delayed_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_thread_stress_legacy_test $(BINDIR)/$(CONFIG)/chttp2_fullstack_uds_writes_done_hangs_with_pending_read_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_bad_hostname_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_and_writes_closed_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_invoke_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_before_invoke_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_in_a_vacuum_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_census_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_disappearing_server_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_inflight_calls_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_tags_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_empty_batch_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_graceful_server_shutdown_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_invoke_large_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_max_concurrent_streams_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_no_op_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_ping_pong_streaming_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_binary_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_with_large_metadata_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_delayed_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_thread_stress_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_writes_done_hangs_with_pending_read_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_accept_and_writes_closed_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_after_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_before_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_cancel_in_a_vacuum_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_census_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_disappearing_server_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_inflight_calls_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_early_server_shutdown_finishes_tags_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_graceful_server_shutdown_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_invoke_large_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_max_concurrent_streams_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_no_op_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_ping_pong_streaming_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_binary_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_response_with_trailing_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_with_large_metadata_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_request_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_delayed_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_thread_stress_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_fullstack_writes_done_hangs_with_pending_read_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_bad_hostname_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_and_writes_closed_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_invoke_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_before_invoke_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_in_a_vacuum_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_census_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_disappearing_server_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_inflight_calls_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_tags_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_empty_batch_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_graceful_server_shutdown_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_invoke_large_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_max_concurrent_streams_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_no_op_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_ping_pong_streaming_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_binary_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_with_large_metadata_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_delayed_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_thread_stress_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_writes_done_hangs_with_pending_read_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_accept_and_writes_closed_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_after_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_before_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_cancel_in_a_vacuum_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_census_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_disappearing_server_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_inflight_calls_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_early_server_shutdown_finishes_tags_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_graceful_server_shutdown_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_invoke_large_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_max_concurrent_streams_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_no_op_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_ping_pong_streaming_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_binary_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_response_with_trailing_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_with_large_metadata_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_request_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_delayed_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_thread_stress_legacy_test $(BINDIR)/$(CONFIG)/chttp2_simple_ssl_with_oauth2_fullstack_writes_done_hangs_with_pending_read_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_bad_hostname_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_and_writes_closed_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_after_invoke_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_before_invoke_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_in_a_vacuum_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_census_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_disappearing_server_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_inflight_calls_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_tags_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_empty_batch_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_graceful_server_shutdown_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_invoke_large_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_max_concurrent_streams_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_no_op_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_ping_pong_streaming_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_response_with_binary_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_response_with_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_response_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_with_large_metadata_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_simple_delayed_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_thread_stress_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_writes_done_hangs_with_pending_read_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_after_accept_and_writes_closed_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_after_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_before_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_cancel_in_a_vacuum_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_census_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_disappearing_server_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_inflight_calls_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_early_server_shutdown_finishes_tags_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_graceful_server_shutdown_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_invoke_large_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_max_concurrent_streams_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_no_op_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_ping_pong_streaming_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_response_with_binary_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_response_with_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_response_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_response_with_trailing_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_with_large_metadata_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_request_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_simple_delayed_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_thread_stress_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_writes_done_hangs_with_pending_read_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_bad_hostname_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_and_writes_closed_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_invoke_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_before_invoke_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_in_a_vacuum_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_census_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_disappearing_server_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_inflight_calls_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_tags_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_empty_batch_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_graceful_server_shutdown_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_invoke_large_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_max_concurrent_streams_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_no_op_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_ping_pong_streaming_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_binary_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_metadata_and_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_with_large_metadata_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_with_payload_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_delayed_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_request_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_thread_stress_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_writes_done_hangs_with_pending_read_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_accept_and_writes_closed_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_after_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_before_invoke_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_cancel_in_a_vacuum_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_census_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_disappearing_server_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_inflight_calls_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_early_server_shutdown_finishes_tags_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_graceful_server_shutdown_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_invoke_large_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_max_concurrent_streams_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_no_op_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_ping_pong_streaming_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_binary_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_response_with_trailing_metadata_and_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_with_large_metadata_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_request_with_payload_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_delayed_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_simple_request_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_thread_stress_legacy_test $(BINDIR)/$(CONFIG)/chttp2_socket_pair_one_byte_at_a_time_writes_done_hangs_with_pending_read_legacy_test
 
-buildtests_cxx: privatelibs_cxx $(BINDIR)/$(CONFIG)/async_end2end_test $(BINDIR)/$(CONFIG)/channel_arguments_test $(BINDIR)/$(CONFIG)/credentials_test $(BINDIR)/$(CONFIG)/cxx_time_test $(BINDIR)/$(CONFIG)/end2end_test $(BINDIR)/$(CONFIG)/generic_end2end_test $(BINDIR)/$(CONFIG)/interop_client $(BINDIR)/$(CONFIG)/interop_server $(BINDIR)/$(CONFIG)/interop_test $(BINDIR)/$(CONFIG)/pubsub_client $(BINDIR)/$(CONFIG)/pubsub_publisher_test $(BINDIR)/$(CONFIG)/pubsub_subscriber_test $(BINDIR)/$(CONFIG)/qps_driver $(BINDIR)/$(CONFIG)/qps_worker $(BINDIR)/$(CONFIG)/status_test $(BINDIR)/$(CONFIG)/thread_pool_test
+buildtests_cxx: privatelibs_cxx $(BINDIR)/$(CONFIG)/async_end2end_test $(BINDIR)/$(CONFIG)/channel_arguments_test $(BINDIR)/$(CONFIG)/cli_call_test $(BINDIR)/$(CONFIG)/credentials_test $(BINDIR)/$(CONFIG)/cxx_time_test $(BINDIR)/$(CONFIG)/end2end_test $(BINDIR)/$(CONFIG)/generic_end2end_test $(BINDIR)/$(CONFIG)/grpc_cli $(BINDIR)/$(CONFIG)/interop_client $(BINDIR)/$(CONFIG)/interop_server $(BINDIR)/$(CONFIG)/interop_test $(BINDIR)/$(CONFIG)/pubsub_client $(BINDIR)/$(CONFIG)/pubsub_publisher_test $(BINDIR)/$(CONFIG)/pubsub_subscriber_test $(BINDIR)/$(CONFIG)/qps_driver $(BINDIR)/$(CONFIG)/qps_worker $(BINDIR)/$(CONFIG)/status_test $(BINDIR)/$(CONFIG)/thread_pool_test
 
 test: test_c test_cxx
 
@@ -1902,6 +1904,8 @@ test_cxx: buildtests_cxx
 	$(Q) $(BINDIR)/$(CONFIG)/async_end2end_test || ( echo test async_end2end_test failed ; exit 1 )
 	$(E) "[RUN]     Testing channel_arguments_test"
 	$(Q) $(BINDIR)/$(CONFIG)/channel_arguments_test || ( echo test channel_arguments_test failed ; exit 1 )
+	$(E) "[RUN]     Testing cli_call_test"
+	$(Q) $(BINDIR)/$(CONFIG)/cli_call_test || ( echo test cli_call_test failed ; exit 1 )
 	$(E) "[RUN]     Testing credentials_test"
 	$(Q) $(BINDIR)/$(CONFIG)/credentials_test || ( echo test credentials_test failed ; exit 1 )
 	$(E) "[RUN]     Testing cxx_time_test"
@@ -2553,6 +2557,7 @@ LIBGRPC_SRC = \
     src/core/surface/byte_buffer_reader.c \
     src/core/surface/call.c \
     src/core/surface/call_details.c \
+    src/core/surface/call_log_batch.c \
     src/core/surface/channel.c \
     src/core/surface/channel_create.c \
     src/core/surface/client.c \
@@ -2699,6 +2704,7 @@ src/core/surface/byte_buffer_queue.c: $(OPENSSL_DEP)
 src/core/surface/byte_buffer_reader.c: $(OPENSSL_DEP)
 src/core/surface/call.c: $(OPENSSL_DEP)
 src/core/surface/call_details.c: $(OPENSSL_DEP)
+src/core/surface/call_log_batch.c: $(OPENSSL_DEP)
 src/core/surface/channel.c: $(OPENSSL_DEP)
 src/core/surface/channel_create.c: $(OPENSSL_DEP)
 src/core/surface/client.c: $(OPENSSL_DEP)
@@ -2861,6 +2867,7 @@ $(OBJDIR)/$(CONFIG)/src/core/surface/byte_buffer_queue.o:
 $(OBJDIR)/$(CONFIG)/src/core/surface/byte_buffer_reader.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/call.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/call_details.o: 
+$(OBJDIR)/$(CONFIG)/src/core/surface/call_log_batch.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/channel.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/channel_create.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/client.o: 
@@ -3036,6 +3043,7 @@ LIBGRPC_UNSECURE_SRC = \
     src/core/surface/byte_buffer_reader.c \
     src/core/surface/call.c \
     src/core/surface/call_details.c \
+    src/core/surface/call_log_batch.c \
     src/core/surface/channel.c \
     src/core/surface/channel_create.c \
     src/core/surface/client.c \
@@ -3175,6 +3183,7 @@ $(OBJDIR)/$(CONFIG)/src/core/surface/byte_buffer_queue.o:
 $(OBJDIR)/$(CONFIG)/src/core/surface/byte_buffer_reader.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/call.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/call_details.o: 
+$(OBJDIR)/$(CONFIG)/src/core/surface/call_log_batch.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/channel.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/channel_create.o: 
 $(OBJDIR)/$(CONFIG)/src/core/surface/client.o: 
@@ -3218,6 +3227,7 @@ LIBGRPC++_SRC = \
     src/cpp/client/client_unary_call.cc \
     src/cpp/client/create_channel.cc \
     src/cpp/client/credentials.cc \
+    src/cpp/client/generic_stub.cc \
     src/cpp/client/insecure_credentials.cc \
     src/cpp/client/internal_stub.cc \
     src/cpp/common/call.cc \
@@ -3306,6 +3316,7 @@ src/cpp/client/client_context.cc: $(OPENSSL_DEP)
 src/cpp/client/client_unary_call.cc: $(OPENSSL_DEP)
 src/cpp/client/create_channel.cc: $(OPENSSL_DEP)
 src/cpp/client/credentials.cc: $(OPENSSL_DEP)
+src/cpp/client/generic_stub.cc: $(OPENSSL_DEP)
 src/cpp/client/insecure_credentials.cc: $(OPENSSL_DEP)
 src/cpp/client/internal_stub.cc: $(OPENSSL_DEP)
 src/cpp/common/call.cc: $(OPENSSL_DEP)
@@ -3372,6 +3383,7 @@ $(OBJDIR)/$(CONFIG)/src/cpp/client/client_context.o:
 $(OBJDIR)/$(CONFIG)/src/cpp/client/client_unary_call.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/create_channel.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/credentials.o: 
+$(OBJDIR)/$(CONFIG)/src/cpp/client/generic_stub.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/insecure_credentials.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/internal_stub.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/common/call.o: 
@@ -3395,6 +3407,7 @@ LIBGRPC++_TEST_UTIL_SRC = \
     $(GENDIR)/test/cpp/util/messages.pb.cc \
     $(GENDIR)/test/cpp/util/echo.pb.cc \
     $(GENDIR)/test/cpp/util/echo_duplicate.pb.cc \
+    test/cpp/util/cli_call.cc \
     test/cpp/util/create_test_channel.cc \
 
 
@@ -3425,6 +3438,7 @@ ifneq ($(OPENSSL_DEP),)
 test/cpp/util/messages.proto: $(OPENSSL_DEP)
 test/cpp/util/echo.proto: $(OPENSSL_DEP)
 test/cpp/util/echo_duplicate.proto: $(OPENSSL_DEP)
+test/cpp/util/cli_call.cc: $(OPENSSL_DEP)
 test/cpp/util/create_test_channel.cc: $(OPENSSL_DEP)
 endif
 
@@ -3453,6 +3467,7 @@ endif
 
 
 
+$(OBJDIR)/$(CONFIG)/test/cpp/util/cli_call.o:     $(GENDIR)/test/cpp/util/messages.pb.cc    $(GENDIR)/test/cpp/util/echo.pb.cc    $(GENDIR)/test/cpp/util/echo_duplicate.pb.cc
 $(OBJDIR)/$(CONFIG)/test/cpp/util/create_test_channel.o:     $(GENDIR)/test/cpp/util/messages.pb.cc    $(GENDIR)/test/cpp/util/echo.pb.cc    $(GENDIR)/test/cpp/util/echo_duplicate.pb.cc
 
 
@@ -3463,6 +3478,7 @@ LIBGRPC++_UNSECURE_SRC = \
     src/cpp/client/client_unary_call.cc \
     src/cpp/client/create_channel.cc \
     src/cpp/client/credentials.cc \
+    src/cpp/client/generic_stub.cc \
     src/cpp/client/insecure_credentials.cc \
     src/cpp/client/internal_stub.cc \
     src/cpp/common/call.cc \
@@ -3566,6 +3582,7 @@ $(OBJDIR)/$(CONFIG)/src/cpp/client/client_context.o:
 $(OBJDIR)/$(CONFIG)/src/cpp/client/client_unary_call.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/create_channel.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/credentials.o: 
+$(OBJDIR)/$(CONFIG)/src/cpp/client/generic_stub.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/insecure_credentials.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/client/internal_stub.o: 
 $(OBJDIR)/$(CONFIG)/src/cpp/common/call.o: 
@@ -3585,30 +3602,30 @@ $(OBJDIR)/$(CONFIG)/src/cpp/util/status.o:
 $(OBJDIR)/$(CONFIG)/src/cpp/util/time.o: 
 
 
-LIBGRPC_PYTHON_PLUGIN_SUPPORT_SRC = \
+LIBGRPC_PLUGIN_SUPPORT_SRC = \
+    src/compiler/cpp_generator.cc \
     src/compiler/python_generator.cc \
+    src/compiler/ruby_generator.cc \
 
-PUBLIC_HEADERS_CXX += \
-    src/compiler/python_generator.h \
 
-LIBGRPC_PYTHON_PLUGIN_SUPPORT_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC_PYTHON_PLUGIN_SUPPORT_SRC))))
+LIBGRPC_PLUGIN_SUPPORT_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGRPC_PLUGIN_SUPPORT_SRC))))
 
 ifeq ($(NO_PROTOBUF),true)
 
 # You can't build a C++ library if you don't have protobuf - a bit overreached, but still okay.
 
-$(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a: protobuf_dep_error
+$(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a: protobuf_dep_error
 
 
 else
 
-$(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a: $(ZLIB_DEP) $(PROTOBUF_DEP) $(LIBGRPC_PYTHON_PLUGIN_SUPPORT_OBJS)
+$(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a: $(ZLIB_DEP) $(PROTOBUF_DEP) $(LIBGRPC_PLUGIN_SUPPORT_OBJS)
 	$(E) "[AR]      Creating $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) rm -f $(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a
-	$(Q) $(AR) rcs $(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a $(LIBGRPC_PYTHON_PLUGIN_SUPPORT_OBJS)
+	$(Q) rm -f $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
+	$(Q) $(AR) rcs $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a $(LIBGRPC_PLUGIN_SUPPORT_OBJS)
 ifeq ($(SYSTEM),Darwin)
-	$(Q) ranlib $(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a
+	$(Q) ranlib $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
 endif
 
 
@@ -3617,10 +3634,12 @@ endif
 endif
 
 ifneq ($(NO_DEPS),true)
--include $(LIBGRPC_PYTHON_PLUGIN_SUPPORT_OBJS:.o=.dep)
+-include $(LIBGRPC_PLUGIN_SUPPORT_OBJS:.o=.dep)
 endif
 
+$(OBJDIR)/$(CONFIG)/src/compiler/cpp_generator.o: 
 $(OBJDIR)/$(CONFIG)/src/compiler/python_generator.o: 
+$(OBJDIR)/$(CONFIG)/src/compiler/ruby_generator.o: 
 
 
 LIBPUBSUB_CLIENT_LIB_SRC = \
@@ -8019,6 +8038,48 @@ endif
 endif
 
 
+CLI_CALL_TEST_SRC = \
+    test/cpp/util/cli_call_test.cc \
+
+CLI_CALL_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(CLI_CALL_TEST_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+$(BINDIR)/$(CONFIG)/cli_call_test: openssl_dep_error
+
+else
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+.
+
+$(BINDIR)/$(CONFIG)/cli_call_test: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/cli_call_test: $(PROTOBUF_DEP) $(CLI_CALL_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(CLI_CALL_TEST_OBJS) $(GTEST_LIB) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/cli_call_test
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/cpp/util/cli_call_test.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_cli_call_test: $(CLI_CALL_TEST_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(CLI_CALL_TEST_OBJS:.o=.dep)
+endif
+endif
+
+
 CREDENTIALS_TEST_SRC = \
     test/cpp/client/credentials_test.cc \
 
@@ -8187,8 +8248,49 @@ endif
 endif
 
 
+GRPC_CLI_SRC = \
+    test/cpp/util/grpc_cli.cc \
+
+GRPC_CLI_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_CLI_SRC))))
+
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL with ALPN.
+
+$(BINDIR)/$(CONFIG)/grpc_cli: openssl_dep_error
+
+else
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+.
+
+$(BINDIR)/$(CONFIG)/grpc_cli: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/grpc_cli: $(PROTOBUF_DEP) $(GRPC_CLI_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(GRPC_CLI_OBJS) $(GTEST_LIB) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/grpc_cli
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/cpp/util/grpc_cli.o:  $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_grpc_cli: $(GRPC_CLI_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(GRPC_CLI_OBJS:.o=.dep)
+endif
+endif
+
+
 GRPC_CPP_PLUGIN_SRC = \
-    src/compiler/cpp_generator.cc \
     src/compiler/cpp_plugin.cc \
 
 GRPC_CPP_PLUGIN_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_CPP_PLUGIN_SRC))))
@@ -8202,15 +8304,14 @@ $(BINDIR)/$(CONFIG)/grpc_cpp_plugin: protobuf_dep_error
 
 else
 
-$(BINDIR)/$(CONFIG)/grpc_cpp_plugin: $(PROTOBUF_DEP) $(GRPC_CPP_PLUGIN_OBJS)
+$(BINDIR)/$(CONFIG)/grpc_cpp_plugin: $(PROTOBUF_DEP) $(GRPC_CPP_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
 	$(E) "[HOSTLD]  Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_CPP_PLUGIN_OBJS) $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_cpp_plugin
+	$(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_CPP_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_cpp_plugin
 
 endif
 
-$(OBJDIR)/$(CONFIG)/src/compiler/cpp_generator.o: 
-$(OBJDIR)/$(CONFIG)/src/compiler/cpp_plugin.o: 
+$(OBJDIR)/$(CONFIG)/src/compiler/cpp_plugin.o:  $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
 
 deps_grpc_cpp_plugin: $(GRPC_CPP_PLUGIN_OBJS:.o=.dep)
 
@@ -8233,14 +8334,14 @@ $(BINDIR)/$(CONFIG)/grpc_python_plugin: protobuf_dep_error
 
 else
 
-$(BINDIR)/$(CONFIG)/grpc_python_plugin: $(PROTOBUF_DEP) $(GRPC_PYTHON_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a
+$(BINDIR)/$(CONFIG)/grpc_python_plugin: $(PROTOBUF_DEP) $(GRPC_PYTHON_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
 	$(E) "[HOSTLD]  Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_PYTHON_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_python_plugin
+	$(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_PYTHON_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_python_plugin
 
 endif
 
-$(OBJDIR)/$(CONFIG)/src/compiler/python_plugin.o:  $(LIBDIR)/$(CONFIG)/libgrpc_python_plugin_support.a
+$(OBJDIR)/$(CONFIG)/src/compiler/python_plugin.o:  $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
 
 deps_grpc_python_plugin: $(GRPC_PYTHON_PLUGIN_OBJS:.o=.dep)
 
@@ -8250,7 +8351,6 @@ endif
 
 
 GRPC_RUBY_PLUGIN_SRC = \
-    src/compiler/ruby_generator.cc \
     src/compiler/ruby_plugin.cc \
 
 GRPC_RUBY_PLUGIN_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_RUBY_PLUGIN_SRC))))
@@ -8264,15 +8364,14 @@ $(BINDIR)/$(CONFIG)/grpc_ruby_plugin: protobuf_dep_error
 
 else
 
-$(BINDIR)/$(CONFIG)/grpc_ruby_plugin: $(PROTOBUF_DEP) $(GRPC_RUBY_PLUGIN_OBJS)
+$(BINDIR)/$(CONFIG)/grpc_ruby_plugin: $(PROTOBUF_DEP) $(GRPC_RUBY_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
 	$(E) "[HOSTLD]  Linking $@"
 	$(Q) mkdir -p `dirname $@`
-	$(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_RUBY_PLUGIN_OBJS) $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_ruby_plugin
+	$(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_RUBY_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_ruby_plugin
 
 endif
 
-$(OBJDIR)/$(CONFIG)/src/compiler/ruby_generator.o: 
-$(OBJDIR)/$(CONFIG)/src/compiler/ruby_plugin.o: 
+$(OBJDIR)/$(CONFIG)/src/compiler/ruby_plugin.o:  $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
 
 deps_grpc_ruby_plugin: $(GRPC_RUBY_PLUGIN_OBJS:.o=.dep)
 
diff --git a/build.json b/build.json
index a46866263f6c5afdc0e6199fbbce8e6eeb0cff25..9e9a1cec67072591763b84089d5c1868bfa93713 100644
--- a/build.json
+++ b/build.json
@@ -52,6 +52,7 @@
         "src/cpp/client/client_unary_call.cc",
         "src/cpp/client/create_channel.cc",
         "src/cpp/client/credentials.cc",
+        "src/cpp/client/generic_stub.cc",
         "src/cpp/client/insecure_credentials.cc",
         "src/cpp/client/internal_stub.cc",
         "src/cpp/common/call.cc",
@@ -233,6 +234,7 @@
         "src/core/surface/byte_buffer_reader.c",
         "src/core/surface/call.c",
         "src/core/surface/call_details.c",
+        "src/core/surface/call_log_batch.c",
         "src/core/surface/channel.c",
         "src/core/surface/channel_create.c",
         "src/core/surface/client.c",
@@ -480,6 +482,7 @@
         "test/cpp/util/messages.proto",
         "test/cpp/util/echo.proto",
         "test/cpp/util/echo_duplicate.proto",
+        "test/cpp/util/cli_call.cc",
         "test/cpp/util/create_test_channel.cc"
       ]
     },
@@ -498,14 +501,24 @@
       "secure": "no"
     },
     {
-      "name": "grpc_python_plugin_support",
+      "name": "grpc_plugin_support",
       "build": "protoc",
       "language": "c++",
-      "public_headers": [
-        "src/compiler/python_generator.h"
+      "headers": [
+        "src/compiler/config.h",
+        "src/compiler/cpp_generator.h",
+        "src/compiler/cpp_generator_helpers.h",
+        "src/compiler/generator_helpers.h",
+        "src/compiler/python_generator.h",
+        "src/compiler/ruby_generator.h",
+        "src/compiler/ruby_generator_helpers-inl.h",
+        "src/compiler/ruby_generator_map-inl.h",
+        "src/compiler/ruby_generator_string-inl.h"
       ],
       "src": [
-        "src/compiler/python_generator.cc"
+        "src/compiler/cpp_generator.cc",
+        "src/compiler/python_generator.cc",
+        "src/compiler/ruby_generator.cc"
       ],
       "deps": [],
       "secure": "no"
@@ -1694,6 +1707,22 @@
         "gpr"
       ]
     },
+    {
+      "name": "cli_call_test",
+      "build": "test",
+      "language": "c++",
+      "src": [
+        "test/cpp/util/cli_call_test.cc"
+      ],
+      "deps": [
+        "grpc++_test_util",
+        "grpc_test_util",
+        "grpc++",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
     {
       "name": "credentials_test",
       "build": "test",
@@ -1754,19 +1783,33 @@
         "gpr"
       ]
     },
+    {
+      "name": "grpc_cli",
+      "build": "test",
+      "run": false,
+      "language": "c++",
+      "src": [
+        "test/cpp/util/grpc_cli.cc"
+      ],
+      "deps": [
+        "grpc++_test_util",
+        "grpc_test_util",
+        "grpc++",
+        "grpc",
+        "gpr_test_util",
+        "gpr"
+      ]
+    },
     {
       "name": "grpc_cpp_plugin",
       "build": "protoc",
       "language": "c++",
-      "headers": [
-        "src/compiler/cpp_generator.h",
-        "src/compiler/cpp_generator_helpers.h"
-      ],
       "src": [
-        "src/compiler/cpp_generator.cc",
         "src/compiler/cpp_plugin.cc"
       ],
-      "deps": [],
+      "deps": [
+        "grpc_plugin_support"
+      ],
       "secure": "no"
     },
     {
@@ -1777,7 +1820,7 @@
         "src/compiler/python_plugin.cc"
       ],
       "deps": [
-        "grpc_python_plugin_support"
+        "grpc_plugin_support"
       ],
       "secure": "no"
     },
@@ -1786,10 +1829,11 @@
       "build": "protoc",
       "language": "c++",
       "src": [
-        "src/compiler/ruby_generator.cc",
         "src/compiler/ruby_plugin.cc"
       ],
-      "deps": [],
+      "deps": [
+        "grpc_plugin_support"
+      ],
       "secure": "no"
     },
     {
diff --git a/examples/pubsub/main.cc b/examples/pubsub/main.cc
index 6f7737e2476ffa04088321e39210bcf7a486182e..cc5076f0a551fb6202568ebb4dc0adde9f6751d6 100644
--- a/examples/pubsub/main.cc
+++ b/examples/pubsub/main.cc
@@ -51,14 +51,14 @@
 #include "examples/pubsub/subscriber.h"
 
 DEFINE_int32(server_port, 443, "Server port.");
-DEFINE_string(server_host,
-              "pubsub-staging.googleapis.com", "Server host to connect to");
+DEFINE_string(server_host, "pubsub-staging.googleapis.com",
+              "Server host to connect to");
 DEFINE_string(project_id, "", "GCE project id such as stoked-keyword-656");
 
 // In some distros, gflags is in the namespace google, and in some others,
 // in gflags. This hack is enabling us to find both.
-namespace google { }
-namespace gflags { }
+namespace google {}
+namespace gflags {}
 using namespace google;
 using namespace gflags;
 
@@ -92,32 +92,32 @@ int main(int argc, char** argv) {
   grpc::string topic = ss.str();
 
   ss.str("");
-  ss << FLAGS_project_id << "/"  << kSubscriptionName;
+  ss << FLAGS_project_id << "/" << kSubscriptionName;
   grpc::string subscription_name = ss.str();
 
   // Clean up test topic and subcription if they exist before.
   grpc::string subscription_topic;
-  if (subscriber.GetSubscription(
-      subscription_name, &subscription_topic).IsOk()) {
+  if (subscriber.GetSubscription(subscription_name, &subscription_topic)
+          .IsOk()) {
     subscriber.DeleteSubscription(subscription_name);
   }
 
   if (publisher.GetTopic(topic).IsOk()) publisher.DeleteTopic(topic);
 
   grpc::Status s = publisher.CreateTopic(topic);
-  gpr_log(GPR_INFO, "Create topic returns code %d, %s",
-          s.code(), s.details().c_str());
+  gpr_log(GPR_INFO, "Create topic returns code %d, %s", s.code(),
+          s.details().c_str());
   GPR_ASSERT(s.IsOk());
 
   s = publisher.GetTopic(topic);
-  gpr_log(GPR_INFO, "Get topic returns code %d, %s",
-          s.code(), s.details().c_str());
+  gpr_log(GPR_INFO, "Get topic returns code %d, %s", s.code(),
+          s.details().c_str());
   GPR_ASSERT(s.IsOk());
 
   std::vector<grpc::string> topics;
   s = publisher.ListTopics(FLAGS_project_id, &topics);
-  gpr_log(GPR_INFO, "List topic returns code %d, %s",
-          s.code(), s.details().c_str());
+  gpr_log(GPR_INFO, "List topic returns code %d, %s", s.code(),
+          s.details().c_str());
   bool topic_found = false;
   for (unsigned int i = 0; i < topics.size(); i++) {
     if (topics[i] == topic) topic_found = true;
@@ -127,27 +127,27 @@ int main(int argc, char** argv) {
   GPR_ASSERT(topic_found);
 
   s = subscriber.CreateSubscription(topic, subscription_name);
-  gpr_log(GPR_INFO, "create subscrption returns code %d, %s",
-          s.code(), s.details().c_str());
+  gpr_log(GPR_INFO, "create subscrption returns code %d, %s", s.code(),
+          s.details().c_str());
   GPR_ASSERT(s.IsOk());
 
   s = publisher.Publish(topic, kMessageData);
-  gpr_log(GPR_INFO, "Publish %s returns code %d, %s",
-          kMessageData, s.code(), s.details().c_str());
+  gpr_log(GPR_INFO, "Publish %s returns code %d, %s", kMessageData, s.code(),
+          s.details().c_str());
   GPR_ASSERT(s.IsOk());
 
   grpc::string data;
   s = subscriber.Pull(subscription_name, &data);
   gpr_log(GPR_INFO, "Pull %s", data.c_str());
 
-  s =  subscriber.DeleteSubscription(subscription_name);
-  gpr_log(GPR_INFO, "Delete subscription returns code %d, %s",
-          s.code(), s.details().c_str());
+  s = subscriber.DeleteSubscription(subscription_name);
+  gpr_log(GPR_INFO, "Delete subscription returns code %d, %s", s.code(),
+          s.details().c_str());
   GPR_ASSERT(s.IsOk());
 
   s = publisher.DeleteTopic(topic);
-  gpr_log(GPR_INFO, "Delete topic returns code %d, %s",
-          s.code(), s.details().c_str());
+  gpr_log(GPR_INFO, "Delete topic returns code %d, %s", s.code(),
+          s.details().c_str());
   GPR_ASSERT(s.IsOk());
 
   subscriber.Shutdown();
diff --git a/examples/pubsub/publisher.cc b/examples/pubsub/publisher.cc
index 308f9a77e5715a8f35fa5150ac0f6d6137840041..458050af739f799f0a58a1c8d555aab31ffaa96a 100644
--- a/examples/pubsub/publisher.cc
+++ b/examples/pubsub/publisher.cc
@@ -51,12 +51,9 @@ namespace examples {
 namespace pubsub {
 
 Publisher::Publisher(std::shared_ptr<ChannelInterface> channel)
-    : stub_(PublisherService::NewStub(channel)) {
-}
+    : stub_(PublisherService::NewStub(channel)) {}
 
-void Publisher::Shutdown() {
-  stub_.reset();
-}
+void Publisher::Shutdown() { stub_.reset(); }
 
 Status Publisher::CreateTopic(const grpc::string& topic) {
   Topic request;
diff --git a/examples/pubsub/publisher_test.cc b/examples/pubsub/publisher_test.cc
index 62442c738477473c895ad28f9838294759f78c5c..ac4921283f4bdfd20fc412ab90a7099d65f5823b 100644
--- a/examples/pubsub/publisher_test.cc
+++ b/examples/pubsub/publisher_test.cc
@@ -31,8 +31,6 @@
  *
  */
 
-#include <google/protobuf/stubs/common.h>
-
 #include <grpc++/channel_arguments.h>
 #include <grpc++/channel_interface.h>
 #include <grpc++/client_context.h>
@@ -84,20 +82,19 @@ class PublisherServiceImpl : public tech::pubsub::PublisherService::Service {
   Status ListTopics(
       ServerContext* context, const ::tech::pubsub::ListTopicsRequest* request,
       ::tech::pubsub::ListTopicsResponse* response) GRPC_OVERRIDE {
-   std::ostringstream ss;
-   ss << "cloud.googleapis.com/project in (/projects/" << kProjectId << ")";
-   EXPECT_EQ(request->query(), ss.str());
-   response->add_topic()->set_name(kTopic);
-   return Status::OK;
- }
-
- Status DeleteTopic(ServerContext* context,
-                    const ::tech::pubsub::DeleteTopicRequest* request,
-                    ::proto2::Empty* response) GRPC_OVERRIDE {
-    EXPECT_EQ(request->topic(), kTopic);
+    std::ostringstream ss;
+    ss << "cloud.googleapis.com/project in (/projects/" << kProjectId << ")";
+    EXPECT_EQ(request->query(), ss.str());
+    response->add_topic()->set_name(kTopic);
     return Status::OK;
- }
+  }
 
+  Status DeleteTopic(ServerContext* context,
+                     const ::tech::pubsub::DeleteTopicRequest* request,
+                     ::proto2::Empty* response) GRPC_OVERRIDE {
+    EXPECT_EQ(request->topic(), kTopic);
+    return Status::OK;
+  }
 };
 
 class PublisherTest : public ::testing::Test {
@@ -107,11 +104,13 @@ class PublisherTest : public ::testing::Test {
     int port = grpc_pick_unused_port_or_die();
     server_address_ << "localhost:" << port;
     ServerBuilder builder;
-    builder.AddListeningPort(server_address_.str(), grpc::InsecureServerCredentials());
+    builder.AddListeningPort(server_address_.str(),
+                             grpc::InsecureServerCredentials());
     builder.RegisterService(&service_);
     server_ = builder.BuildAndStart();
 
-    channel_ = CreateChannel(server_address_.str(), grpc::InsecureCredentials(), ChannelArguments());
+    channel_ = CreateChannel(server_address_.str(), grpc::InsecureCredentials(),
+                             ChannelArguments());
 
     publisher_.reset(new grpc::examples::pubsub::Publisher(channel_));
   }
diff --git a/examples/pubsub/subscriber.cc b/examples/pubsub/subscriber.cc
index 29f6635b7c9221baf8367973c5f23ad81d986fae..d9e0292ba0ff94f4267eb4f789ad34a1f83ea12b 100644
--- a/examples/pubsub/subscriber.cc
+++ b/examples/pubsub/subscriber.cc
@@ -49,12 +49,9 @@ namespace examples {
 namespace pubsub {
 
 Subscriber::Subscriber(std::shared_ptr<ChannelInterface> channel)
-    : stub_(SubscriberService::NewStub(channel)) {
-}
+    : stub_(SubscriberService::NewStub(channel)) {}
 
-void Subscriber::Shutdown() {
-  stub_.reset();
-}
+void Subscriber::Shutdown() { stub_.reset(); }
 
 Status Subscriber::CreateSubscription(const grpc::string& topic,
                                       const grpc::string& name) {
diff --git a/examples/pubsub/subscriber_test.cc b/examples/pubsub/subscriber_test.cc
index b8dd1f9486e3f228c5d9b915922d91de71ff0d76..9ab60ed6a760eabf34c946da8cafb74f623b0f71 100644
--- a/examples/pubsub/subscriber_test.cc
+++ b/examples/pubsub/subscriber_test.cc
@@ -31,8 +31,6 @@
  *
  */
 
-#include <google/protobuf/stubs/common.h>
-
 #include <grpc++/channel_arguments.h>
 #include <grpc++/channel_interface.h>
 #include <grpc++/client_context.h>
@@ -95,7 +93,6 @@ class SubscriberServiceImpl : public tech::pubsub::SubscriberService::Service {
                      proto2::Empty* response) GRPC_OVERRIDE {
     return Status::OK;
   }
-
 };
 
 class SubscriberTest : public ::testing::Test {
@@ -105,11 +102,13 @@ class SubscriberTest : public ::testing::Test {
     int port = grpc_pick_unused_port_or_die();
     server_address_ << "localhost:" << port;
     ServerBuilder builder;
-    builder.AddListeningPort(server_address_.str(), grpc::InsecureServerCredentials());
+    builder.AddListeningPort(server_address_.str(),
+                             grpc::InsecureServerCredentials());
     builder.RegisterService(&service_);
     server_ = builder.BuildAndStart();
 
-    channel_ = CreateChannel(server_address_.str(), grpc::InsecureCredentials(), ChannelArguments());
+    channel_ = CreateChannel(server_address_.str(), grpc::InsecureCredentials(),
+                             ChannelArguments());
 
     subscriber_.reset(new grpc::examples::pubsub::Subscriber(channel_));
   }
@@ -129,17 +128,15 @@ class SubscriberTest : public ::testing::Test {
 };
 
 TEST_F(SubscriberTest, TestSubscriber) {
-  EXPECT_TRUE(subscriber_->CreateSubscription(kTopic,
-                                              kSubscriptionName).IsOk());
+  EXPECT_TRUE(
+      subscriber_->CreateSubscription(kTopic, kSubscriptionName).IsOk());
 
   grpc::string topic;
-  EXPECT_TRUE(subscriber_->GetSubscription(kSubscriptionName,
-                                           &topic).IsOk());
+  EXPECT_TRUE(subscriber_->GetSubscription(kSubscriptionName, &topic).IsOk());
   EXPECT_EQ(topic, kTopic);
 
   grpc::string data;
-  EXPECT_TRUE(subscriber_->Pull(kSubscriptionName,
-                                &data).IsOk());
+  EXPECT_TRUE(subscriber_->Pull(kSubscriptionName, &data).IsOk());
 
   EXPECT_TRUE(subscriber_->DeleteSubscription(kSubscriptionName).IsOk());
 }
diff --git a/include/grpc++/config.h b/include/grpc++/config.h
index 35bf50736477b536f3a2176eae5162f387b58c01..8ef5d71bfac4202a1c2d38bf97e2d68e79f95083 100644
--- a/include/grpc++/config.h
+++ b/include/grpc++/config.h
@@ -65,6 +65,28 @@
   ::google::protobuf::io::ZeroCopyInputStream
 #endif
 
+#ifndef __clang__
+#ifdef __GNUC__
+#if (__GNUC__ * 100 + __GNUC_MINOR__ < 406)
+#define GRPC_NO_NULLPTR
+#endif
+#endif
+#endif
+
+#ifdef GRPC_NO_NULLPTR
+#include <memory>
+const class {
+public:
+  template <class T> operator T*() const {return static_cast<T *>(0);}
+  template <class T> operator std::unique_ptr<T>() const {
+    return std::unique_ptr<T>(static_cast<T *>(0));
+  }
+  operator bool() const {return false;}
+private:
+  void operator&() const = delete;
+} nullptr = {};
+#endif
+
 namespace grpc {
 
 typedef GRPC_CUSTOM_STRING string;
diff --git a/include/grpc++/generic_stub.h b/include/grpc++/generic_stub.h
index d4c8380ad4558cb25148bbfd9845a63b5c412bfe..c34e1fcf55fc1966a1871fcff7b5a3b070cdca05 100644
--- a/include/grpc++/generic_stub.h
+++ b/include/grpc++/generic_stub.h
@@ -39,6 +39,7 @@
 
 namespace grpc {
 
+class CompletionQueue;
 typedef ClientAsyncReaderWriter<ByteBuffer, ByteBuffer>
     GenericClientAsyncReaderWriter;
 
@@ -51,7 +52,8 @@ class GenericStub GRPC_FINAL {
 
   // begin a call to a named method
   std::unique_ptr<GenericClientAsyncReaderWriter> Call(
-      ClientContext* context, const grpc::string& method);
+      ClientContext* context, const grpc::string& method,
+      CompletionQueue* cq, void* tag);
 
  private:
   std::shared_ptr<ChannelInterface> channel_;
diff --git a/src/compiler/cpp_generator.cc b/src/compiler/cpp_generator.cc
index d5004624d42df69d17562840723e366c26ec0cbf..0a84c7352007f175c26d17c95cd4fa5e8c0af6e4 100644
--- a/src/compiler/cpp_generator.cc
+++ b/src/compiler/cpp_generator.cc
@@ -111,7 +111,8 @@ bool HasBidiStreaming(const grpc::protobuf::FileDescriptor *file) {
 }
 }  // namespace
 
-grpc::string GetHeaderIncludes(const grpc::protobuf::FileDescriptor *file) {
+grpc::string GetHeaderIncludes(const grpc::protobuf::FileDescriptor *file,
+                               const Parameters &params) {
   grpc::string temp =
       "#include <grpc++/impl/internal_stub.h>\n"
       "#include <grpc++/impl/service_type.h>\n"
@@ -158,7 +159,7 @@ grpc::string GetHeaderIncludes(const grpc::protobuf::FileDescriptor *file) {
   return temp;
 }
 
-grpc::string GetSourceIncludes() {
+grpc::string GetSourceIncludes(const Parameters &param) {
   return "#include <grpc++/async_unary_call.h>\n"
          "#include <grpc++/channel_interface.h>\n"
          "#include <grpc++/impl/client_unary_call.h>\n"
@@ -353,16 +354,27 @@ void PrintHeaderService(grpc::protobuf::io::Printer *printer,
   printer->Print("};\n");
 }
 
-grpc::string GetHeaderServices(const grpc::protobuf::FileDescriptor *file) {
+grpc::string GetHeaderServices(const grpc::protobuf::FileDescriptor *file,
+                               const Parameters &params) {
   grpc::string output;
   grpc::protobuf::io::StringOutputStream output_stream(&output);
   grpc::protobuf::io::Printer printer(&output_stream, '$');
   std::map<grpc::string, grpc::string> vars;
 
+  if (!params.services_namespace.empty()) {
+    vars["services_namespace"] = params.services_namespace;
+    printer.Print(vars, "\nnamespace $services_namespace$ {\n\n");
+  }
+
   for (int i = 0; i < file->service_count(); ++i) {
     PrintHeaderService(&printer, file->service(i), &vars);
     printer.Print("\n");
   }
+
+  if (!params.services_namespace.empty()) {
+    printer.Print(vars, "}  // namespace $services_namespace$\n\n");
+  }
+
   return output;
 }
 
@@ -376,18 +388,18 @@ void PrintSourceClientMethod(grpc::protobuf::io::Printer *printer,
       grpc_cpp_generator::ClassName(method->output_type(), true);
   if (NoStreaming(method)) {
     printer->Print(*vars,
-                   "::grpc::Status $Service$::Stub::$Method$("
+                   "::grpc::Status $ns$$Service$::Stub::$Method$("
                    "::grpc::ClientContext* context, "
                    "const $Request$& request, $Response$* response) {\n");
     printer->Print(*vars,
                    "  return ::grpc::BlockingUnaryCall(channel(),"
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$]), "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$]), "
                    "context, request, response);\n"
                    "}\n\n");
     printer->Print(
         *vars,
         "std::unique_ptr< ::grpc::ClientAsyncResponseReader< $Response$>> "
-        "$Service$::Stub::Async$Method$(::grpc::ClientContext* context, "
+        "$ns$$Service$::Stub::Async$Method$(::grpc::ClientContext* context, "
         "const $Request$& request, "
         "::grpc::CompletionQueue* cq, void* tag) {\n");
     printer->Print(*vars,
@@ -395,32 +407,32 @@ void PrintSourceClientMethod(grpc::protobuf::io::Printer *printer,
                    "::grpc::ClientAsyncResponseReader< $Response$>>(new "
                    "::grpc::ClientAsyncResponseReader< $Response$>("
                    "channel(), cq, "
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$]), "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$]), "
                    "context, request, tag));\n"
                    "}\n\n");
   } else if (ClientOnlyStreaming(method)) {
     printer->Print(*vars,
                    "std::unique_ptr< ::grpc::ClientWriter< $Request$>> "
-                   "$Service$::Stub::$Method$("
+                   "$ns$$Service$::Stub::$Method$("
                    "::grpc::ClientContext* context, $Response$* response) {\n");
     printer->Print(*vars,
                    "  return std::unique_ptr< ::grpc::ClientWriter< "
                    "$Request$>>(new ::grpc::ClientWriter< $Request$>("
                    "channel(),"
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$], "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$], "
                    "::grpc::RpcMethod::RpcType::CLIENT_STREAMING), "
                    "context, response));\n"
                    "}\n\n");
     printer->Print(*vars,
                    "std::unique_ptr< ::grpc::ClientAsyncWriter< $Request$>> "
-                   "$Service$::Stub::Async$Method$("
+                   "$ns$$Service$::Stub::Async$Method$("
                    "::grpc::ClientContext* context, $Response$* response, "
                    "::grpc::CompletionQueue* cq, void* tag) {\n");
     printer->Print(*vars,
                    "  return std::unique_ptr< ::grpc::ClientAsyncWriter< "
                    "$Request$>>(new ::grpc::ClientAsyncWriter< $Request$>("
                    "channel(), cq, "
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$], "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$], "
                    "::grpc::RpcMethod::RpcType::CLIENT_STREAMING), "
                    "context, response, tag));\n"
                    "}\n\n");
@@ -428,26 +440,26 @@ void PrintSourceClientMethod(grpc::protobuf::io::Printer *printer,
     printer->Print(
         *vars,
         "std::unique_ptr< ::grpc::ClientReader< $Response$>> "
-        "$Service$::Stub::$Method$("
+        "$ns$$Service$::Stub::$Method$("
         "::grpc::ClientContext* context, const $Request$& request) {\n");
     printer->Print(*vars,
                    "  return std::unique_ptr< ::grpc::ClientReader< "
                    "$Response$>>(new ::grpc::ClientReader< $Response$>("
                    "channel(),"
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$], "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$], "
                    "::grpc::RpcMethod::RpcType::SERVER_STREAMING), "
                    "context, request));\n"
                    "}\n\n");
     printer->Print(*vars,
                    "std::unique_ptr< ::grpc::ClientAsyncReader< $Response$>> "
-                   "$Service$::Stub::Async$Method$("
+                   "$ns$$Service$::Stub::Async$Method$("
                    "::grpc::ClientContext* context, const $Request$& request, "
                    "::grpc::CompletionQueue* cq, void* tag) {\n");
     printer->Print(*vars,
                    "  return std::unique_ptr< ::grpc::ClientAsyncReader< "
                    "$Response$>>(new ::grpc::ClientAsyncReader< $Response$>("
                    "channel(), cq, "
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$], "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$], "
                    "::grpc::RpcMethod::RpcType::SERVER_STREAMING), "
                    "context, request, tag));\n"
                    "}\n\n");
@@ -455,27 +467,27 @@ void PrintSourceClientMethod(grpc::protobuf::io::Printer *printer,
     printer->Print(
         *vars,
         "std::unique_ptr< ::grpc::ClientReaderWriter< $Request$, $Response$>> "
-        "$Service$::Stub::$Method$(::grpc::ClientContext* context) {\n");
+        "$ns$$Service$::Stub::$Method$(::grpc::ClientContext* context) {\n");
     printer->Print(*vars,
                    "  return std::unique_ptr< ::grpc::ClientReaderWriter< "
                    "$Request$, $Response$>>(new ::grpc::ClientReaderWriter< "
                    "$Request$, $Response$>("
                    "channel(),"
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$], "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$], "
                    "::grpc::RpcMethod::RpcType::BIDI_STREAMING), "
                    "context));\n"
                    "}\n\n");
     printer->Print(*vars,
                    "std::unique_ptr< ::grpc::ClientAsyncReaderWriter< "
                    "$Request$, $Response$>> "
-                   "$Service$::Stub::Async$Method$(::grpc::ClientContext* context, "
+                   "$ns$$Service$::Stub::Async$Method$(::grpc::ClientContext* context, "
                    "::grpc::CompletionQueue* cq, void* tag) {\n");
     printer->Print(*vars,
                    "  return std::unique_ptr< ::grpc::ClientAsyncReaderWriter< "
                    "$Request$, $Response$>>(new "
                    "::grpc::ClientAsyncReaderWriter< $Request$, $Response$>("
                    "channel(), cq, "
-                   "::grpc::RpcMethod($Service$_method_names[$Idx$], "
+                   "::grpc::RpcMethod($prefix$$Service$_method_names[$Idx$], "
                    "::grpc::RpcMethod::RpcType::BIDI_STREAMING), "
                    "context, tag));\n"
                    "}\n\n");
@@ -492,7 +504,7 @@ void PrintSourceServerMethod(grpc::protobuf::io::Printer *printer,
       grpc_cpp_generator::ClassName(method->output_type(), true);
   if (NoStreaming(method)) {
     printer->Print(*vars,
-                   "::grpc::Status $Service$::Service::$Method$("
+                   "::grpc::Status $ns$$Service$::Service::$Method$("
                    "::grpc::ServerContext* context, "
                    "const $Request$* request, $Response$* response) {\n");
     printer->Print(
@@ -501,7 +513,7 @@ void PrintSourceServerMethod(grpc::protobuf::io::Printer *printer,
     printer->Print("}\n\n");
   } else if (ClientOnlyStreaming(method)) {
     printer->Print(*vars,
-                   "::grpc::Status $Service$::Service::$Method$("
+                   "::grpc::Status $ns$$Service$::Service::$Method$("
                    "::grpc::ServerContext* context, "
                    "::grpc::ServerReader< $Request$>* reader, "
                    "$Response$* response) {\n");
@@ -511,7 +523,7 @@ void PrintSourceServerMethod(grpc::protobuf::io::Printer *printer,
     printer->Print("}\n\n");
   } else if (ServerOnlyStreaming(method)) {
     printer->Print(*vars,
-                   "::grpc::Status $Service$::Service::$Method$("
+                   "::grpc::Status $ns$$Service$::Service::$Method$("
                    "::grpc::ServerContext* context, "
                    "const $Request$* request, "
                    "::grpc::ServerWriter< $Response$>* writer) {\n");
@@ -521,7 +533,7 @@ void PrintSourceServerMethod(grpc::protobuf::io::Printer *printer,
     printer->Print("}\n\n");
   } else if (BidiStreaming(method)) {
     printer->Print(*vars,
-                   "::grpc::Status $Service$::Service::$Method$("
+                   "::grpc::Status $ns$$Service$::Service::$Method$("
                    "::grpc::ServerContext* context, "
                    "::grpc::ServerReaderWriter< $Response$, $Request$>* "
                    "stream) {\n");
@@ -543,7 +555,7 @@ void PrintSourceServerAsyncMethod(
       grpc_cpp_generator::ClassName(method->output_type(), true);
   if (NoStreaming(method)) {
     printer->Print(*vars,
-                   "void $Service$::AsyncService::Request$Method$("
+                   "void $ns$$Service$::AsyncService::Request$Method$("
                    "::grpc::ServerContext* context, "
                    "$Request$* request, "
                    "::grpc::ServerAsyncResponseWriter< $Response$>* response, "
@@ -554,7 +566,7 @@ void PrintSourceServerAsyncMethod(
     printer->Print("}\n\n");
   } else if (ClientOnlyStreaming(method)) {
     printer->Print(*vars,
-                   "void $Service$::AsyncService::Request$Method$("
+                   "void $ns$$Service$::AsyncService::Request$Method$("
                    "::grpc::ServerContext* context, "
                    "::grpc::ServerAsyncReader< $Response$, $Request$>* reader, "
                    "::grpc::CompletionQueue* cq, void* tag) {\n");
@@ -564,7 +576,7 @@ void PrintSourceServerAsyncMethod(
     printer->Print("}\n\n");
   } else if (ServerOnlyStreaming(method)) {
     printer->Print(*vars,
-                   "void $Service$::AsyncService::Request$Method$("
+                   "void $ns$$Service$::AsyncService::Request$Method$("
                    "::grpc::ServerContext* context, "
                    "$Request$* request, "
                    "::grpc::ServerAsyncWriter< $Response$>* writer, "
@@ -576,7 +588,7 @@ void PrintSourceServerAsyncMethod(
   } else if (BidiStreaming(method)) {
     printer->Print(
         *vars,
-        "void $Service$::AsyncService::Request$Method$("
+        "void $ns$$Service$::AsyncService::Request$Method$("
         "::grpc::ServerContext* context, "
         "::grpc::ServerAsyncReaderWriter< $Response$, $Request$>* stream, "
         "::grpc::CompletionQueue* cq, void *tag) {\n");
@@ -592,7 +604,7 @@ void PrintSourceService(grpc::protobuf::io::Printer *printer,
                         std::map<grpc::string, grpc::string> *vars) {
   (*vars)["Service"] = service->name();
 
-  printer->Print(*vars, "static const char* $Service$_method_names[] = {\n");
+  printer->Print(*vars, "static const char* $prefix$$Service$_method_names[] = {\n");
   for (int i = 0; i < service->method_count(); ++i) {
     (*vars)["Method"] = service->method(i)->name();
     printer->Print(*vars, "  \"/$Package$$Service$/$Method$\",\n");
@@ -601,9 +613,9 @@ void PrintSourceService(grpc::protobuf::io::Printer *printer,
 
   printer->Print(
       *vars,
-      "std::unique_ptr< $Service$::Stub> $Service$::NewStub("
+      "std::unique_ptr< $ns$$Service$::Stub> $ns$$Service$::NewStub("
       "const std::shared_ptr< ::grpc::ChannelInterface>& channel) {\n"
-      "  std::unique_ptr< $Service$::Stub> stub(new $Service$::Stub());\n"
+      "  std::unique_ptr< $ns$$Service$::Stub> stub(new $ns$$Service$::Stub());\n"
       "  stub->set_channel(channel);\n"
       "  return stub;\n"
       "}\n\n");
@@ -615,12 +627,12 @@ void PrintSourceService(grpc::protobuf::io::Printer *printer,
   (*vars)["MethodCount"] = as_string(service->method_count());
   printer->Print(
       *vars,
-      "$Service$::AsyncService::AsyncService(::grpc::CompletionQueue* cq) : "
-      "::grpc::AsynchronousService(cq, $Service$_method_names, $MethodCount$) "
+      "$ns$$Service$::AsyncService::AsyncService(::grpc::CompletionQueue* cq) : "
+      "::grpc::AsynchronousService(cq, $prefix$$Service$_method_names, $MethodCount$) "
       "{}\n\n");
 
   printer->Print(*vars,
-                 "$Service$::Service::~Service() {\n"
+                 "$ns$$Service$::Service::~Service() {\n"
                  "  delete service_;\n"
                  "}\n\n");
   for (int i = 0; i < service->method_count(); ++i) {
@@ -629,7 +641,7 @@ void PrintSourceService(grpc::protobuf::io::Printer *printer,
     PrintSourceServerAsyncMethod(printer, service->method(i), vars);
   }
   printer->Print(*vars,
-                 "::grpc::RpcService* $Service$::Service::service() {\n");
+                 "::grpc::RpcService* $ns$$Service$::Service::service() {\n");
   printer->Indent();
   printer->Print(
       "if (service_ != nullptr) {\n"
@@ -648,52 +660,52 @@ void PrintSourceService(grpc::protobuf::io::Printer *printer,
       printer->Print(
           *vars,
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
-          "    $Service$_method_names[$Idx$],\n"
+          "    $prefix$$Service$_method_names[$Idx$],\n"
           "    ::grpc::RpcMethod::NORMAL_RPC,\n"
-          "    new ::grpc::RpcMethodHandler< $Service$::Service, $Request$, "
+          "    new ::grpc::RpcMethodHandler< $ns$$Service$::Service, $Request$, "
           "$Response$>(\n"
-          "        std::function< ::grpc::Status($Service$::Service*, "
+          "        std::function< ::grpc::Status($ns$$Service$::Service*, "
           "::grpc::ServerContext*, const $Request$*, $Response$*)>("
-          "&$Service$::Service::$Method$), this),\n"
+          "&$ns$$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
     } else if (ClientOnlyStreaming(method)) {
       printer->Print(
           *vars,
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
-          "    $Service$_method_names[$Idx$],\n"
+          "    $prefix$$Service$_method_names[$Idx$],\n"
           "    ::grpc::RpcMethod::CLIENT_STREAMING,\n"
           "    new ::grpc::ClientStreamingHandler< "
-          "$Service$::Service, $Request$, $Response$>(\n"
-          "        std::function< ::grpc::Status($Service$::Service*, "
+          "$ns$$Service$::Service, $Request$, $Response$>(\n"
+          "        std::function< ::grpc::Status($ns$$Service$::Service*, "
           "::grpc::ServerContext*, "
           "::grpc::ServerReader< $Request$>*, $Response$*)>("
-          "&$Service$::Service::$Method$), this),\n"
+          "&$ns$$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
     } else if (ServerOnlyStreaming(method)) {
       printer->Print(
           *vars,
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
-          "    $Service$_method_names[$Idx$],\n"
+          "    $prefix$$Service$_method_names[$Idx$],\n"
           "    ::grpc::RpcMethod::SERVER_STREAMING,\n"
           "    new ::grpc::ServerStreamingHandler< "
-          "$Service$::Service, $Request$, $Response$>(\n"
-          "        std::function< ::grpc::Status($Service$::Service*, "
+          "$ns$$Service$::Service, $Request$, $Response$>(\n"
+          "        std::function< ::grpc::Status($ns$$Service$::Service*, "
           "::grpc::ServerContext*, "
           "const $Request$*, ::grpc::ServerWriter< $Response$>*)>("
-          "&$Service$::Service::$Method$), this),\n"
+          "&$ns$$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
     } else if (BidiStreaming(method)) {
       printer->Print(
           *vars,
           "service_->AddMethod(new ::grpc::RpcServiceMethod(\n"
-          "    $Service$_method_names[$Idx$],\n"
+          "    $prefix$$Service$_method_names[$Idx$],\n"
           "    ::grpc::RpcMethod::BIDI_STREAMING,\n"
           "    new ::grpc::BidiStreamingHandler< "
-          "$Service$::Service, $Request$, $Response$>(\n"
-          "        std::function< ::grpc::Status($Service$::Service*, "
+          "$ns$$Service$::Service, $Request$, $Response$>(\n"
+          "        std::function< ::grpc::Status($ns$$Service$::Service*, "
           "::grpc::ServerContext*, "
           "::grpc::ServerReaderWriter< $Response$, $Request$>*)>("
-          "&$Service$::Service::$Method$), this),\n"
+          "&$ns$$Service$::Service::$Method$), this),\n"
           "    new $Request$, new $Response$));\n");
     }
   }
@@ -702,7 +714,8 @@ void PrintSourceService(grpc::protobuf::io::Printer *printer,
   printer->Print("}\n\n");
 }
 
-grpc::string GetSourceServices(const grpc::protobuf::FileDescriptor *file) {
+grpc::string GetSourceServices(const grpc::protobuf::FileDescriptor *file,
+                               const Parameters &params) {
   grpc::string output;
   grpc::protobuf::io::StringOutputStream output_stream(&output);
   grpc::protobuf::io::Printer printer(&output_stream, '$');
@@ -713,6 +726,13 @@ grpc::string GetSourceServices(const grpc::protobuf::FileDescriptor *file) {
   if (!file->package().empty()) {
     vars["Package"].append(".");
   }
+  if (!params.services_namespace.empty()) {
+    vars["ns"] = params.services_namespace + "::";
+    vars["prefix"] = params.services_namespace;
+  } else {
+    vars["ns"] = "";
+    vars["prefix"] = "";
+  }
 
   for (int i = 0; i < file->service_count(); ++i) {
     PrintSourceService(&printer, file->service(i), &vars);
diff --git a/src/compiler/cpp_generator.h b/src/compiler/cpp_generator.h
index 2ecdb5c47e27a1ebbaae19328270413d39772f21..04ad71c0673336352b1a60e408d99df6c8d0f403 100644
--- a/src/compiler/cpp_generator.h
+++ b/src/compiler/cpp_generator.h
@@ -38,17 +38,26 @@
 
 namespace grpc_cpp_generator {
 
+// Contains all the parameters that are parsed from the command line.
+struct Parameters {
+  // Puts the service into a namespace
+  grpc::string services_namespace;
+};
+
 // Return the includes needed for generated header file.
-grpc::string GetHeaderIncludes(const grpc::protobuf::FileDescriptor *file);
+grpc::string GetHeaderIncludes(const grpc::protobuf::FileDescriptor *file,
+                               const Parameters &params);
 
 // Return the includes needed for generated source file.
-grpc::string GetSourceIncludes();
+grpc::string GetSourceIncludes(const Parameters &params);
 
 // Return the services for generated header file.
-grpc::string GetHeaderServices(const grpc::protobuf::FileDescriptor *file);
+grpc::string GetHeaderServices(const grpc::protobuf::FileDescriptor *file,
+                               const Parameters &params);
 
 // Return the services for generated source file.
-grpc::string GetSourceServices(const grpc::protobuf::FileDescriptor *file);
+grpc::string GetSourceServices(const grpc::protobuf::FileDescriptor *file,
+                               const Parameters &params);
 
 }  // namespace grpc_cpp_generator
 
diff --git a/src/compiler/cpp_plugin.cc b/src/compiler/cpp_plugin.cc
index 5b83aa85cf1f34d94a06f3743730c33138be7f27..acbe128213a2b34faf97d59f7c7d3e09d171b5d9 100644
--- a/src/compiler/cpp_plugin.cc
+++ b/src/compiler/cpp_plugin.cc
@@ -58,18 +58,42 @@ class CppGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
       return false;
     }
 
+    if (file->service_count() == 0) {
+      // No services.  Do nothing.
+      return true;
+    }
+
+    grpc_cpp_generator::Parameters generator_parameters;
+
+    if (!parameter.empty()) {
+      std::vector<grpc::string> parameters_list =
+        grpc_generator::tokenize(parameter, ",");
+      for (auto parameter_string = parameters_list.begin();
+           parameter_string != parameters_list.end();
+           parameter_string++) {
+        std::vector<grpc::string> param =
+          grpc_generator::tokenize(*parameter_string, "=");
+        if (param[0] == "services_namespace") {
+          generator_parameters.services_namespace = param[1];
+        } else {
+          *error = grpc::string("Unknown parameter: ") + *parameter_string;
+          return false;
+        }
+      }
+    }
+
     grpc::string file_name = grpc_generator::StripProto(file->name());
 
     // Generate .pb.h
     Insert(context, file_name + ".pb.h", "includes",
-           grpc_cpp_generator::GetHeaderIncludes(file));
+           grpc_cpp_generator::GetHeaderIncludes(file, generator_parameters));
     Insert(context, file_name + ".pb.h", "namespace_scope",
-           grpc_cpp_generator::GetHeaderServices(file));
+           grpc_cpp_generator::GetHeaderServices(file, generator_parameters));
     // Generate .pb.cc
     Insert(context, file_name + ".pb.cc", "includes",
-           grpc_cpp_generator::GetSourceIncludes());
+           grpc_cpp_generator::GetSourceIncludes(generator_parameters));
     Insert(context, file_name + ".pb.cc", "namespace_scope",
-           grpc_cpp_generator::GetSourceServices(file));
+           grpc_cpp_generator::GetSourceServices(file, generator_parameters));
 
     return true;
   }
diff --git a/src/compiler/generator_helpers.h b/src/compiler/generator_helpers.h
index 1e6727dd4c0a0337c5ef0534427c12dd0f61548e..30857891c77dee85042c021f1ecdd4bd1bf115de 100644
--- a/src/compiler/generator_helpers.h
+++ b/src/compiler/generator_helpers.h
@@ -75,6 +75,26 @@ inline grpc::string StringReplace(grpc::string str, const grpc::string &from,
   return str;
 }
 
+inline std::vector<grpc::string> tokenize(const grpc::string &input,
+                                          const grpc::string &delimiters) {
+  std::vector<grpc::string> tokens;
+  size_t pos, last_pos = 0;
+
+  for (;;) {
+    bool done = false;
+    pos = input.find_first_of(delimiters, last_pos);
+    if (pos == grpc::string::npos) {
+      done = true;
+      pos = input.length();
+    }
+
+    tokens.push_back(input.substr(last_pos, pos - last_pos));
+    if (done) return tokens;
+
+    last_pos = pos + 1;
+  }
+}
+
 }  // namespace grpc_generator
 
 #endif  // GRPC_INTERNAL_COMPILER_GENERATOR_HELPERS_H
diff --git a/src/compiler/python_generator.cc b/src/compiler/python_generator.cc
index c2d4cda31a78d746f2ad51040484a032fec0e9ae..748417e4776617b401be686e2a2f6a0818842926 100644
--- a/src/compiler/python_generator.cc
+++ b/src/compiler/python_generator.cc
@@ -309,17 +309,20 @@ bool PrintServerFactory(const grpc::string& package_qualified_service_name,
           make_pair(method->name(), output_message_module_and_class));
     }
     out->Print("method_service_descriptions = {\n");
-    for (auto& name_and_description_constructor :
-         method_description_constructors) {
+    for (auto name_and_description_constructor =
+	   method_description_constructors.begin();
+	 name_and_description_constructor !=
+	   method_description_constructors.end();
+	 name_and_description_constructor++) {
       IndentScope raii_descriptions_indent(out);
-      const grpc::string method_name = name_and_description_constructor.first;
+      const grpc::string method_name = name_and_description_constructor->first;
       auto input_message_module_and_class =
           input_message_modules_and_classes.find(method_name);
       auto output_message_module_and_class =
           output_message_modules_and_classes.find(method_name);
       out->Print("\"$Method$\": utilities.$Constructor$(\n", "Method",
                  method_name, "Constructor",
-                 name_and_description_constructor.second);
+                 name_and_description_constructor->second);
       {
         IndentScope raii_description_arguments_indent(out);
         out->Print("servicer.$Method$,\n", "Method", method_name);
@@ -387,17 +390,20 @@ bool PrintStubFactory(const grpc::string& package_qualified_service_name,
           make_pair(method->name(), output_message_module_and_class));
     }
     out->Print("method_invocation_descriptions = {\n");
-    for (auto& name_and_description_constructor :
-         method_description_constructors) {
+    for (auto name_and_description_constructor =
+	   method_description_constructors.begin();
+	 name_and_description_constructor !=
+	   method_description_constructors.end();
+	 name_and_description_constructor++) {
       IndentScope raii_descriptions_indent(out);
-      const grpc::string method_name = name_and_description_constructor.first;
+      const grpc::string method_name = name_and_description_constructor->first;
       auto input_message_module_and_class =
           input_message_modules_and_classes.find(method_name);
       auto output_message_module_and_class =
           output_message_modules_and_classes.find(method_name);
       out->Print("\"$Method$\": utilities.$Constructor$(\n", "Method",
                  method_name, "Constructor",
-                 name_and_description_constructor.second);
+                 name_and_description_constructor->second);
       {
         IndentScope raii_description_arguments_indent(out);
         out->Print(
diff --git a/src/compiler/ruby_generator.cc b/src/compiler/ruby_generator.cc
index 32b6a8d8e4a0537db5b62583e6040c4b4cbece36..a0bb92848b877f095d07d3ad714fc373972b7ffa 100644
--- a/src/compiler/ruby_generator.cc
+++ b/src/compiler/ruby_generator.cc
@@ -32,24 +32,20 @@
  */
 
 #include <cctype>
-#include <string>
 #include <map>
 #include <vector>
 
+#include "src/compiler/config.h"
 #include "src/compiler/ruby_generator.h"
 #include "src/compiler/ruby_generator_helpers-inl.h"
 #include "src/compiler/ruby_generator_map-inl.h"
 #include "src/compiler/ruby_generator_string-inl.h"
-#include <google/protobuf/io/printer.h>
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <google/protobuf/descriptor.pb.h>
-#include <google/protobuf/descriptor.h>
-
-using google::protobuf::FileDescriptor;
-using google::protobuf::ServiceDescriptor;
-using google::protobuf::MethodDescriptor;
-using google::protobuf::io::Printer;
-using google::protobuf::io::StringOutputStream;
+
+using grpc::protobuf::FileDescriptor;
+using grpc::protobuf::ServiceDescriptor;
+using grpc::protobuf::MethodDescriptor;
+using grpc::protobuf::io::Printer;
+using grpc::protobuf::io::StringOutputStream;
 using std::map;
 using std::vector;
 
@@ -57,38 +53,38 @@ namespace grpc_ruby_generator {
 namespace {
 
 // Prints out the method using the ruby gRPC DSL.
-void PrintMethod(const MethodDescriptor *method, const std::string &package,
+void PrintMethod(const MethodDescriptor *method, const grpc::string &package,
                  Printer *out) {
-  std::string input_type = RubyTypeOf(method->input_type()->name(), package);
+  grpc::string input_type = RubyTypeOf(method->input_type()->name(), package);
   if (method->client_streaming()) {
     input_type = "stream(" + input_type + ")";
   }
-  std::string output_type = RubyTypeOf(method->output_type()->name(), package);
+  grpc::string output_type = RubyTypeOf(method->output_type()->name(), package);
   if (method->server_streaming()) {
     output_type = "stream(" + output_type + ")";
   }
-  std::map<std::string, std::string> method_vars =
+  std::map<grpc::string, grpc::string> method_vars =
       ListToDict({"mth.name", method->name(), "input.type", input_type,
                   "output.type", output_type, });
   out->Print(method_vars, "rpc :$mth.name$, $input.type$, $output.type$\n");
 }
 
 // Prints out the service using the ruby gRPC DSL.
-void PrintService(const ServiceDescriptor *service, const std::string &package,
+void PrintService(const ServiceDescriptor *service, const grpc::string &package,
                   Printer *out) {
   if (service->method_count() == 0) {
     return;
   }
 
   // Begin the service module
-  std::map<std::string, std::string> module_vars =
+  std::map<grpc::string, grpc::string> module_vars =
       ListToDict({"module.name", CapitalizeFirst(service->name()), });
   out->Print(module_vars, "module $module.name$\n");
   out->Indent();
 
   // TODO(temiola): add documentation
-  std::string doc = "TODO: add proto service documentation here";
-  std::map<std::string, std::string> template_vars =
+  grpc::string doc = "TODO: add proto service documentation here";
+  std::map<grpc::string, grpc::string> template_vars =
       ListToDict({"Documentation", doc, });
   out->Print("\n");
   out->Print(template_vars, "# $Documentation$\n");
@@ -101,7 +97,7 @@ void PrintService(const ServiceDescriptor *service, const std::string &package,
   out->Print("\n");
   out->Print("self.marshal_class_method = :encode\n");
   out->Print("self.unmarshal_class_method = :decode\n");
-  std::map<std::string, std::string> pkg_vars =
+  std::map<grpc::string, grpc::string> pkg_vars =
       ListToDict({"service.name", service->name(), "pkg.name", package, });
   out->Print(pkg_vars, "self.service_name = '$pkg.name$.$service.name$'\n");
   out->Print("\n");
@@ -121,8 +117,8 @@ void PrintService(const ServiceDescriptor *service, const std::string &package,
 
 }  // namespace
 
-std::string GetServices(const FileDescriptor *file) {
-  std::string output;
+grpc::string GetServices(const FileDescriptor *file) {
+  grpc::string output;
   StringOutputStream output_stream(&output);
   Printer out(&output_stream, '$');
 
@@ -133,7 +129,7 @@ std::string GetServices(const FileDescriptor *file) {
   }
 
   // Write out a file header.
-  std::map<std::string, std::string> header_comment_vars = ListToDict(
+  std::map<grpc::string, grpc::string> header_comment_vars = ListToDict(
       {"file.name", file->name(), "file.package", file->package(), });
   out.Print("# Generated by the protocol buffer compiler.  DO NOT EDIT!\n");
   out.Print(header_comment_vars,
@@ -144,15 +140,15 @@ std::string GetServices(const FileDescriptor *file) {
   // Write out require statemment to import the separately generated file
   // that defines the messages used by the service. This is generated by the
   // main ruby plugin.
-  std::map<std::string, std::string> dep_vars =
+  std::map<grpc::string, grpc::string> dep_vars =
       ListToDict({"dep.name", MessagesRequireName(file), });
   out.Print(dep_vars, "require '$dep.name$'\n");
 
   // Write out services within the modules
   out.Print("\n");
-  std::vector<std::string> modules = Split(file->package(), '.');
+  std::vector<grpc::string> modules = Split(file->package(), '.');
   for (size_t i = 0; i < modules.size(); ++i) {
-    std::map<std::string, std::string> module_vars =
+    std::map<grpc::string, grpc::string> module_vars =
         ListToDict({"module.name", CapitalizeFirst(modules[i]), });
     out.Print(module_vars, "module $module.name$\n");
     out.Indent();
diff --git a/src/compiler/ruby_generator.h b/src/compiler/ruby_generator.h
index 4dd38e0c274a1d8d44786764ce9f3a65b5edddd4..a2ab36d4d99dad5b9b1976b1568b1c31810b9a1f 100644
--- a/src/compiler/ruby_generator.h
+++ b/src/compiler/ruby_generator.h
@@ -34,17 +34,11 @@
 #ifndef GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_H
 #define GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_H
 
-#include <string>
-
-namespace google {
-namespace protobuf {
-class FileDescriptor;
-}  // namespace protobuf
-}  // namespace google
+#include "src/compiler/config.h"
 
 namespace grpc_ruby_generator {
 
-std::string GetServices(const google::protobuf::FileDescriptor *file);
+grpc::string GetServices(const grpc::protobuf::FileDescriptor *file);
 
 }  // namespace grpc_ruby_generator
 
diff --git a/src/compiler/ruby_generator_helpers-inl.h b/src/compiler/ruby_generator_helpers-inl.h
index f3a087b3f8efa7668e49a9203f538d0762ce251e..9da7cab3c7f3dd5d33b023f67fc26a50cbd8686d 100644
--- a/src/compiler/ruby_generator_helpers-inl.h
+++ b/src/compiler/ruby_generator_helpers-inl.h
@@ -34,15 +34,13 @@
 #ifndef GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_HELPERS_INL_H
 #define GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_HELPERS_INL_H
 
-#include <string>
-
-#include <google/protobuf/descriptor.h>
+#include "src/compiler/config.h"
 #include "src/compiler/ruby_generator_string-inl.h"
 
 namespace grpc_ruby_generator {
 
-inline bool ServicesFilename(const google::protobuf::FileDescriptor *file,
-                             std::string *file_name_or_error) {
+inline bool ServicesFilename(const grpc::protobuf::FileDescriptor *file,
+                             grpc::string *file_name_or_error) {
   // Get output file name.
   static const unsigned proto_suffix_length = 6;  // length of ".proto"
   if (file->name().size() > proto_suffix_length &&
@@ -57,8 +55,8 @@ inline bool ServicesFilename(const google::protobuf::FileDescriptor *file,
   }
 }
 
-inline std::string MessagesRequireName(
-    const google::protobuf::FileDescriptor *file) {
+inline grpc::string MessagesRequireName(
+    const grpc::protobuf::FileDescriptor *file) {
   return Replace(file->name(), ".proto", "");
 }
 
diff --git a/src/compiler/ruby_generator_map-inl.h b/src/compiler/ruby_generator_map-inl.h
index f902b6d98f80b12345eb9a3987ecd74f53d2982d..6b87774f2136b576a47fdd822b4d1dc9163359f2 100644
--- a/src/compiler/ruby_generator_map-inl.h
+++ b/src/compiler/ruby_generator_map-inl.h
@@ -34,11 +34,12 @@
 #ifndef GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_MAP_INL_H
 #define GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_MAP_INL_H
 
+#include "src/compiler/config.h"
+
 #include <iostream>
 #include <initializer_list>
 #include <map>
 #include <ostream>  // NOLINT
-#include <string>
 #include <vector>
 
 using std::initializer_list;
@@ -49,18 +50,18 @@ namespace grpc_ruby_generator {
 
 // Converts an initializer list of the form { key0, value0, key1, value1, ... }
 // into a map of key* to value*. Is merely a readability helper for later code.
-inline std::map<std::string, std::string> ListToDict(
-    const initializer_list<std::string> &values) {
+inline std::map<grpc::string, grpc::string> ListToDict(
+    const initializer_list<grpc::string> &values) {
   if (values.size() % 2 != 0) {
     std::cerr << "Not every 'key' has a value in `values`."
               << std::endl;
   }
-  std::map<std::string, std::string> value_map;
+  std::map<grpc::string, grpc::string> value_map;
   auto value_iter = values.begin();
   for (unsigned i = 0; i < values.size() / 2; ++i) {
-    std::string key = *value_iter;
+    grpc::string key = *value_iter;
     ++value_iter;
-    std::string value = *value_iter;
+    grpc::string value = *value_iter;
     value_map[key] = value;
     ++value_iter;
   }
diff --git a/src/compiler/ruby_generator_string-inl.h b/src/compiler/ruby_generator_string-inl.h
index bdd314c16e5c809e8f43f8c22a6ca7948605f4b0..8da3a88da2937d1cb6d33956be378d927715f0bd 100644
--- a/src/compiler/ruby_generator_string-inl.h
+++ b/src/compiler/ruby_generator_string-inl.h
@@ -34,8 +34,9 @@
 #ifndef GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_STRING_INL_H
 #define GRPC_INTERNAL_COMPILER_RUBY_GENERATOR_STRING_INL_H
 
+#include "src/compiler/config.h"
+
 #include <algorithm>
-#include <string>
 #include <sstream>
 #include <vector>
 
@@ -45,10 +46,10 @@ using std::transform;
 namespace grpc_ruby_generator {
 
 // Split splits a string using char into elems.
-inline std::vector<std::string> &Split(const std::string &s, char delim,
-                                       std::vector<std::string> *elems) {
+inline std::vector<grpc::string> &Split(const grpc::string &s, char delim,
+                                        std::vector<grpc::string> *elems) {
   std::stringstream ss(s);
-  std::string item;
+  grpc::string item;
   while (getline(ss, item, delim)) {
     elems->push_back(item);
   }
@@ -56,17 +57,17 @@ inline std::vector<std::string> &Split(const std::string &s, char delim,
 }
 
 // Split splits a string using char, returning the result in a vector.
-inline std::vector<std::string> Split(const std::string &s, char delim) {
-  std::vector<std::string> elems;
+inline std::vector<grpc::string> Split(const grpc::string &s, char delim) {
+  std::vector<grpc::string> elems;
   Split(s, delim, &elems);
   return elems;
 }
 
 // Replace replaces from with to in s.
-inline std::string Replace(std::string s, const std::string &from,
-                           const std::string &to) {
+inline grpc::string Replace(grpc::string s, const grpc::string &from,
+                            const grpc::string &to) {
   size_t start_pos = s.find(from);
-  if (start_pos == std::string::npos) {
+  if (start_pos == grpc::string::npos) {
     return s;
   }
   s.replace(start_pos, from.length(), to);
@@ -74,10 +75,10 @@ inline std::string Replace(std::string s, const std::string &from,
 }
 
 // ReplaceAll replaces all instances of search with replace in s.
-inline std::string ReplaceAll(std::string s, const std::string &search,
-                              const std::string &replace) {
+inline grpc::string ReplaceAll(grpc::string s, const grpc::string &search,
+                               const grpc::string &replace) {
   size_t pos = 0;
-  while ((pos = s.find(search, pos)) != std::string::npos) {
+  while ((pos = s.find(search, pos)) != grpc::string::npos) {
     s.replace(pos, search.length(), replace);
     pos += replace.length();
   }
@@ -85,10 +86,10 @@ inline std::string ReplaceAll(std::string s, const std::string &search,
 }
 
 // ReplacePrefix replaces from with to in s if search is a prefix of s.
-inline bool ReplacePrefix(std::string *s, const std::string &from,
-                          const std::string &to) {
+inline bool ReplacePrefix(grpc::string *s, const grpc::string &from,
+                          const grpc::string &to) {
   size_t start_pos = s->find(from);
-  if (start_pos == std::string::npos || start_pos != 0) {
+  if (start_pos == grpc::string::npos || start_pos != 0) {
     return false;
   }
   s->replace(start_pos, from.length(), to);
@@ -96,7 +97,7 @@ inline bool ReplacePrefix(std::string *s, const std::string &from,
 }
 
 // CapitalizeFirst capitalizes the first char in a string.
-inline std::string CapitalizeFirst(std::string s) {
+inline grpc::string CapitalizeFirst(grpc::string s) {
   if (s.empty()) {
     return s;
   }
@@ -105,15 +106,15 @@ inline std::string CapitalizeFirst(std::string s) {
 }
 
 // RubyTypeOf updates a proto type to the required ruby equivalent.
-inline std::string RubyTypeOf(const std::string &a_type,
-                              const std::string &package) {
-  std::string res(a_type);
+inline grpc::string RubyTypeOf(const grpc::string &a_type,
+                               const grpc::string &package) {
+  grpc::string res(a_type);
   ReplacePrefix(&res, package, "");  // remove the leading package if present
   ReplacePrefix(&res, ".", "");      // remove the leading . (no package)
-  if (res.find('.') == std::string::npos) {
+  if (res.find('.') == grpc::string::npos) {
     return res;
   } else {
-    std::vector<std::string> prefixes_and_type = Split(res, '.');
+    std::vector<grpc::string> prefixes_and_type = Split(res, '.');
     for (unsigned int i = 0; i < prefixes_and_type.size(); ++i) {
       if (i != 0) {
         res += "::";  // switch '.' to the ruby module delim
diff --git a/src/compiler/ruby_plugin.cc b/src/compiler/ruby_plugin.cc
index 4a6e9f7a5d38c1d82962ba3ca5a8b5b2995c292a..bd10d46e9ca774a6aab57e63a112e1293d2eed35 100644
--- a/src/compiler/ruby_plugin.cc
+++ b/src/compiler/ruby_plugin.cc
@@ -32,43 +32,35 @@
  */
 
 // Generates Ruby gRPC service interface out of Protobuf IDL.
-//
-// This is a Proto2 compiler plugin.  See net/proto2/compiler/proto/plugin.proto
-// and net/proto2/compiler/public/plugin.h for more information on plugins.
 
 #include <memory>
-#include <string>
 
+#include "src/compiler/config.h"
 #include "src/compiler/ruby_generator.h"
 #include "src/compiler/ruby_generator_helpers-inl.h"
-#include <google/protobuf/compiler/code_generator.h>
-#include <google/protobuf/compiler/plugin.h>
-#include <google/protobuf/io/coded_stream.h>
-#include <google/protobuf/io/zero_copy_stream.h>
-#include <google/protobuf/descriptor.h>
 
-class RubyGrpcGenerator : public google::protobuf::compiler::CodeGenerator {
+class RubyGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
  public:
   RubyGrpcGenerator() {}
   ~RubyGrpcGenerator() {}
 
-  bool Generate(const google::protobuf::FileDescriptor *file,
-                const std::string &parameter,
-                google::protobuf::compiler::GeneratorContext *context,
-                std::string *error) const {
-    std::string code = grpc_ruby_generator::GetServices(file);
+  bool Generate(const grpc::protobuf::FileDescriptor *file,
+                const grpc::string &parameter,
+                grpc::protobuf::compiler::GeneratorContext *context,
+                grpc::string *error) const {
+    grpc::string code = grpc_ruby_generator::GetServices(file);
     if (code.size() == 0) {
       return true;  // don't generate a file if there are no services
     }
 
     // Get output file name.
-    std::string file_name;
+    grpc::string file_name;
     if (!grpc_ruby_generator::ServicesFilename(file, &file_name)) {
       return false;
     }
-    std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> output(
+    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> output(
         context->Open(file_name));
-    google::protobuf::io::CodedOutputStream coded_out(output.get());
+    grpc::protobuf::io::CodedOutputStream coded_out(output.get());
     coded_out.WriteRaw(code.data(), code.size());
     return true;
   }
@@ -76,5 +68,5 @@ class RubyGrpcGenerator : public google::protobuf::compiler::CodeGenerator {
 
 int main(int argc, char *argv[]) {
   RubyGrpcGenerator generator;
-  return google::protobuf::compiler::PluginMain(argc, argv, &generator);
+  return grpc::protobuf::compiler::PluginMain(argc, argv, &generator);
 }
diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c
index f565cbf3aebf830f9a69ecae186ce1f54261c3e9..9da8b333ca0b642029419864420e7b6bfefbc806 100644
--- a/src/core/channel/http_server_filter.c
+++ b/src/core/channel/http_server_filter.c
@@ -189,7 +189,8 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem,
         /* translate host to :authority since :authority may be
            omitted */
         grpc_mdelem *authority = grpc_mdelem_from_metadata_strings(
-            channeld->mdctx, channeld->authority_key, op->data.metadata->value);
+            channeld->mdctx, grpc_mdstr_ref(channeld->authority_key),
+            grpc_mdstr_ref(op->data.metadata->value));
         grpc_mdelem_unref(op->data.metadata);
         op->data.metadata = authority;
         /* pass the event up */
diff --git a/src/core/iomgr/iocp_windows.c b/src/core/iomgr/iocp_windows.c
index 8b019e804961c398d73cbb140763184cd1030309..aec626509aa611bd6d622990af9dc745749d7996 100644
--- a/src/core/iomgr/iocp_windows.c
+++ b/src/core/iomgr/iocp_windows.c
@@ -52,10 +52,11 @@ static OVERLAPPED g_iocp_custom_overlap;
 
 static gpr_event g_shutdown_iocp;
 static gpr_event g_iocp_done;
+static gpr_atm g_orphans = 0;
 
 static HANDLE g_iocp;
 
-static int do_iocp_work() {
+static void do_iocp_work() {
   BOOL success;
   DWORD bytes = 0;
   DWORD flags = 0;
@@ -71,14 +72,14 @@ static int do_iocp_work() {
                                       gpr_time_to_millis(wait_time));
   if (!success && !overlapped) {
     /* The deadline got attained. */
-    return 0;
+    return;
   }
   GPR_ASSERT(completion_key && overlapped);
   if (overlapped == &g_iocp_custom_overlap) {
     if (completion_key == (ULONG_PTR) &g_iocp_kick_token) {
       /* We were awoken from a kick. */
       gpr_log(GPR_DEBUG, "do_iocp_work - got a kick");
-      return 1;
+      return;
     }
     gpr_log(GPR_ERROR, "Unknown custom completion key.");
     abort();
@@ -97,8 +98,13 @@ static int do_iocp_work() {
   }
   success = WSAGetOverlappedResult(socket->socket, &info->overlapped, &bytes,
                                    FALSE, &flags);
-  gpr_log(GPR_DEBUG, "bytes: %u, flags: %u - op %s", bytes, flags,
-          success ? "succeeded" : "failed");
+  gpr_log(GPR_DEBUG, "bytes: %u, flags: %u - op %s %s", bytes, flags,
+          success ? "succeeded" : "failed", socket->orphan ? "orphan" : "");
+  if (socket->orphan) {
+    grpc_winsocket_destroy(socket);
+    gpr_atm_full_fetch_add(&g_orphans, -1);
+    return;
+  }
   info->bytes_transfered = bytes;
   info->wsa_error = success ? 0 : WSAGetLastError();
   GPR_ASSERT(overlapped == &info->overlapped);
@@ -113,12 +119,10 @@ static int do_iocp_work() {
   }
   gpr_mu_unlock(&socket->state_mu);
   if (f) f(opaque, 1);
-
-  return 1;
 }
 
 static void iocp_loop(void *p) {
-  while (!gpr_event_get(&g_shutdown_iocp)) {
+  while (gpr_atm_acq_load(&g_orphans) || !gpr_event_get(&g_shutdown_iocp)) {
     grpc_maybe_call_delayed_callbacks(NULL, 1);
     do_iocp_work();
   }
@@ -138,13 +142,19 @@ void grpc_iocp_init(void) {
   gpr_thd_new(&id, iocp_loop, NULL, NULL);
 }
 
-void grpc_iocp_shutdown(void) {
+void grpc_iocp_kick(void) {
   BOOL success;
-  gpr_event_set(&g_shutdown_iocp, (void *)1);
+
   success = PostQueuedCompletionStatus(g_iocp, 0,
                                        (ULONG_PTR) &g_iocp_kick_token,
                                        &g_iocp_custom_overlap);
   GPR_ASSERT(success);
+}
+
+void grpc_iocp_shutdown(void) {
+  BOOL success;
+  gpr_event_set(&g_shutdown_iocp, (void *)1);
+  grpc_iocp_kick();
   gpr_event_wait(&g_iocp_done, gpr_inf_future);
   success = CloseHandle(g_iocp);
   GPR_ASSERT(success);
@@ -166,6 +176,10 @@ void grpc_iocp_add_socket(grpc_winsocket *socket) {
   GPR_ASSERT(ret == g_iocp);
 }
 
+void grpc_iocp_socket_orphan(grpc_winsocket *socket) {
+  gpr_atm_full_fetch_add(&g_orphans, 1);
+}
+
 static void socket_notify_on_iocp(grpc_winsocket *socket,
                                   void(*cb)(void *, int), void *opaque,
                                   grpc_winsocket_callback_info *info) {
diff --git a/src/core/iomgr/iocp_windows.h b/src/core/iomgr/iocp_windows.h
index 33133193a105ee70d497d5311d9250f3f04831e8..fa3f5eee10ee36afa7531c4a103dbc6176d53938 100644
--- a/src/core/iomgr/iocp_windows.h
+++ b/src/core/iomgr/iocp_windows.h
@@ -42,6 +42,7 @@
 void grpc_iocp_init(void);
 void grpc_iocp_shutdown(void);
 void grpc_iocp_add_socket(grpc_winsocket *);
+void grpc_iocp_socket_orphan(grpc_winsocket *);
 
 void grpc_socket_notify_on_write(grpc_winsocket *, void(*cb)(void *, int success),
                                  void *opaque);
diff --git a/src/core/iomgr/iomgr.c b/src/core/iomgr/iomgr.c
index 058685b295d0090bc2bd644761514d1f9bf969b2..d0e6706fbd7820b7a48d7a850049152f3cc88646 100644
--- a/src/core/iomgr/iomgr.c
+++ b/src/core/iomgr/iomgr.c
@@ -117,7 +117,16 @@ void grpc_iomgr_shutdown(void) {
       gpr_mu_lock(&g_mu);
     }
     if (g_refs) {
-      if (gpr_cv_wait(&g_rcv, &g_mu, shutdown_deadline) && g_cbs_head == NULL) {
+      int timeout = 0;
+      gpr_timespec short_deadline = gpr_time_add(gpr_now(),
+                                                 gpr_time_from_millis(100));
+      while (gpr_cv_wait(&g_rcv, &g_mu, short_deadline) && g_cbs_head == NULL) {
+        if (gpr_time_cmp(gpr_now(), shutdown_deadline) > 0) {
+          timeout = 1;
+          break;
+        }
+      }
+      if (timeout) {
         gpr_log(GPR_DEBUG,
                 "Failed to free %d iomgr objects before shutdown deadline: "
                 "memory leaks are likely",
diff --git a/src/core/iomgr/socket_windows.c b/src/core/iomgr/socket_windows.c
index 99f38b0e032b43d193db424fa7870f880ca7f6c2..22dad41783fd2f4a908d90a544e8aa3e57f083f7 100644
--- a/src/core/iomgr/socket_windows.c
+++ b/src/core/iomgr/socket_windows.c
@@ -55,7 +55,7 @@ grpc_winsocket *grpc_winsocket_create(SOCKET socket) {
   return r;
 }
 
-void shutdown_op(grpc_winsocket_callback_info *info) {
+static void shutdown_op(grpc_winsocket_callback_info *info) {
   if (!info->cb) return;
   grpc_iomgr_add_delayed_callback(info->cb, info->opaque, 0);
 }
@@ -68,8 +68,13 @@ void grpc_winsocket_shutdown(grpc_winsocket *socket) {
 
 void grpc_winsocket_orphan(grpc_winsocket *socket) {
   gpr_log(GPR_DEBUG, "grpc_winsocket_orphan");
+  grpc_iocp_socket_orphan(socket);
+  socket->orphan = 1;
   grpc_iomgr_unref();
   closesocket(socket->socket);
+}
+
+void grpc_winsocket_destroy(grpc_winsocket *socket) {
   gpr_mu_destroy(&socket->state_mu);
   gpr_free(socket);
 }
diff --git a/src/core/iomgr/socket_windows.h b/src/core/iomgr/socket_windows.h
index d4776ab10f898f088fafe3c5a5f3d29ac94ba97a..cbae91692cbba76aba65bcb0544011f96633a209 100644
--- a/src/core/iomgr/socket_windows.h
+++ b/src/core/iomgr/socket_windows.h
@@ -57,12 +57,13 @@ typedef struct grpc_winsocket_callback_info {
 typedef struct grpc_winsocket {
   SOCKET socket;
 
-  int added_to_iocp;
-
   grpc_winsocket_callback_info write_info;
   grpc_winsocket_callback_info read_info;
 
   gpr_mu state_mu;
+
+  int added_to_iocp;
+  int orphan;
 } grpc_winsocket;
 
 /* Create a wrapped windows handle.
@@ -71,5 +72,6 @@ grpc_winsocket *grpc_winsocket_create(SOCKET socket);
 
 void grpc_winsocket_shutdown(grpc_winsocket *socket);
 void grpc_winsocket_orphan(grpc_winsocket *socket);
+void grpc_winsocket_destroy(grpc_winsocket *socket);
 
 #endif  /* GRPC_INTERNAL_CORE_IOMGR_SOCKET_WINDOWS_H */
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index cfce943794072f8ffa04e9af4880a1f8ebe856f8..dba63058b837b80de6a1d83fa502e308fecf23a4 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -1006,6 +1006,8 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
   const grpc_op *op;
   grpc_ioreq *req;
 
+  GRPC_CALL_LOG_BATCH(GPR_INFO, call, ops, nops, tag);
+
   if (nops == 0) {
     grpc_cq_begin_op(call->cq, call, GRPC_OP_COMPLETE);
     grpc_cq_end_op_complete(call->cq, tag, call, do_nothing, NULL, GRPC_OP_OK);
diff --git a/src/core/surface/call.h b/src/core/surface/call.h
index cb81cb52c2541887b8415114a31adb9c839203f6..06434f87ac8c09751b3c49ccc98c099449640d7a 100644
--- a/src/core/surface/call.h
+++ b/src/core/surface/call.h
@@ -119,4 +119,13 @@ grpc_call_stack *grpc_call_get_call_stack(grpc_call *call);
 /* Given the top call_element, get the call object. */
 grpc_call *grpc_call_from_top_element(grpc_call_element *surface_element);
 
+extern int grpc_trace_batch;
+
+void grpc_call_log_batch(char *file, int line, gpr_log_severity severity,
+                         grpc_call *call, const grpc_op *ops, size_t nops,
+                         void *tag);
+
+#define GRPC_CALL_LOG_BATCH(sev, call, ops, nops, tag) \
+  if (grpc_trace_batch) grpc_call_log_batch(sev, call, ops, nops, tag)
+
 #endif  /* GRPC_INTERNAL_CORE_SURFACE_CALL_H */
diff --git a/src/core/surface/call_log_batch.c b/src/core/surface/call_log_batch.c
new file mode 100644
index 0000000000000000000000000000000000000000..a33583a12d1f1665630730550d2f0a75649ba3e5
--- /dev/null
+++ b/src/core/surface/call_log_batch.c
@@ -0,0 +1,121 @@
+/*
+ *
+ * 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 "src/core/surface/call.h"
+
+#include "src/core/support/string.h"
+#include <grpc/support/alloc.h>
+
+int grpc_trace_batch = 0;
+
+static void add_metadata(gpr_strvec *b, const grpc_metadata *md, size_t count) {
+  size_t i;
+  for(i = 0; i < count; i++) {
+    gpr_strvec_add(b, gpr_strdup("\nkey="));
+    gpr_strvec_add(b, gpr_strdup(md[i].key));
+
+    gpr_strvec_add(b, gpr_strdup(" value="));
+    gpr_strvec_add(b, gpr_hexdump(md[i].value, md[i].value_length,
+                                  GPR_HEXDUMP_PLAINTEXT));
+  }
+}
+
+char *grpc_op_string(const grpc_op *op) {
+  char *tmp;
+  char *out;
+
+  gpr_strvec b;
+  gpr_strvec_init(&b);
+
+  switch (op->op) {
+    case GRPC_OP_SEND_INITIAL_METADATA:
+      gpr_strvec_add(&b, gpr_strdup("SEND_INITIAL_METADATA"));
+      add_metadata(&b, op->data.send_initial_metadata.metadata,
+                   op->data.send_initial_metadata.count);
+      break;
+    case GRPC_OP_SEND_MESSAGE:
+      gpr_asprintf(&tmp, "SEND_MESSAGE ptr=%p", op->data.send_message);
+      gpr_strvec_add(&b, tmp);
+      break;
+    case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
+      gpr_strvec_add(&b, gpr_strdup("SEND_CLOSE_FROM_CLIENT"));
+      break;
+    case GRPC_OP_SEND_STATUS_FROM_SERVER:
+      gpr_asprintf(&tmp, "SEND_STATUS_FROM_SERVER status=%d details=%s",
+                   op->data.send_status_from_server.status,
+                   op->data.send_status_from_server.status_details);
+      gpr_strvec_add(&b, tmp);
+      add_metadata(&b, op->data.send_status_from_server.trailing_metadata,
+                   op->data.send_status_from_server.trailing_metadata_count);
+      break;
+    case GRPC_OP_RECV_INITIAL_METADATA:
+      gpr_asprintf(&tmp, "RECV_INITIAL_METADATA ptr=%p",
+                   op->data.recv_initial_metadata);
+      gpr_strvec_add(&b, tmp);
+      break;
+    case GRPC_OP_RECV_MESSAGE:
+      gpr_asprintf(&tmp, "RECV_MESSAGE ptr=%p", op->data.recv_message);
+      gpr_strvec_add(&b, tmp);
+      break;
+    case GRPC_OP_RECV_STATUS_ON_CLIENT:
+      gpr_asprintf(&tmp,
+                   "RECV_STATUS_ON_CLIENT metadata=%p status=%p details=%p",
+                   op->data.recv_status_on_client.trailing_metadata,
+                   op->data.recv_status_on_client.status,
+                   op->data.recv_status_on_client.status_details);
+      gpr_strvec_add(&b, tmp);
+      break;
+    case GRPC_OP_RECV_CLOSE_ON_SERVER:
+      gpr_asprintf(&tmp, "RECV_CLOSE_ON_SERVER cancelled=%p",
+                   op->data.recv_close_on_server.cancelled);
+      gpr_strvec_add(&b, tmp);
+  }
+  out = gpr_strvec_flatten(&b, NULL);
+  gpr_strvec_destroy(&b);
+
+  return out;
+}
+
+void grpc_call_log_batch(char *file, int line, gpr_log_severity severity,
+                         grpc_call *call, const grpc_op *ops, size_t nops,
+                         void *tag) {
+  char *tmp;
+  size_t i;
+  gpr_log(file, line, severity,
+          "grpc_call_start_batch(%p, %p, %d, 0x%x)", call, ops, nops, tag);
+  for(i = 0; i < nops; i++) {
+    tmp = grpc_op_string(&ops[i]);
+    gpr_log(file, line, severity, "ops[%d]: %s", i, tmp);
+    gpr_free(tmp);
+  }
+}
diff --git a/src/core/surface/init.c b/src/core/surface/init.c
index e48c4202e5831004453791d7004a0756879048de..d4f0eb40e8e717b195f57586cdd0ed247c892f50 100644
--- a/src/core/surface/init.c
+++ b/src/core/surface/init.c
@@ -36,6 +36,7 @@
 #include "src/core/debug/trace.h"
 #include "src/core/statistics/census_interface.h"
 #include "src/core/channel/channel_stack.h"
+#include "src/core/surface/call.h"
 #include "src/core/surface/init.h"
 #include "src/core/surface/surface_trace.h"
 #include "src/core/transport/chttp2_transport.h"
@@ -57,6 +58,7 @@ void grpc_init(void) {
     grpc_register_tracer("channel", &grpc_trace_channel);
     grpc_register_tracer("surface", &grpc_surface_trace);
     grpc_register_tracer("http", &grpc_http_trace);
+    grpc_register_tracer("batch", &grpc_trace_batch);
     grpc_security_pre_init();
     grpc_tracer_init("GRPC_TRACE");
     grpc_iomgr_init();
@@ -82,4 +84,3 @@ int grpc_is_initialized(void) {
   gpr_mu_unlock(&g_init_mu);
   return r;
 }
-
diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c
index 33645ca8b866193c5f0f82c9654eb794eae989c1..018ddc4456399fc79bf502ae9621bfffa733051b 100644
--- a/src/core/tsi/ssl_transport_security.c
+++ b/src/core/tsi/ssl_transport_security.c
@@ -567,7 +567,8 @@ static tsi_result populate_ssl_context(
     EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
     if (!SSL_CTX_set_tmp_ecdh(context, ecdh)) {
       gpr_log(GPR_ERROR, "Could not set ephemeral ECDH key.");
-      result = TSI_INTERNAL_ERROR;
+      EC_KEY_free(ecdh);
+      return TSI_INTERNAL_ERROR;
     }
     SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
     EC_KEY_free(ecdh);
@@ -604,6 +605,7 @@ static tsi_result build_alpn_protocol_name_list(
   unsigned char* current;
   *protocol_name_list = NULL;
   *protocol_name_list_length = 0;
+  if (num_alpn_protocols == 0) return TSI_INVALID_ARGUMENT;
   for (i = 0; i < num_alpn_protocols; i++) {
     if (alpn_protocols_lengths[i] == 0) {
       gpr_log(GPR_ERROR, "Invalid 0-length protocol name.");
diff --git a/src/php/lib/autoload.php b/src/cpp/client/generic_stub.cc
old mode 100755
new mode 100644
similarity index 76%
rename from src/php/lib/autoload.php
rename to src/cpp/client/generic_stub.cc
index 42eb33d65b72a18764ec0ff85458957bc9bd6874..3bf7bdf45fb9c5e0d278efdc3d88fae307d81db7
--- a/src/php/lib/autoload.php
+++ b/src/cpp/client/generic_stub.cc
@@ -1,4 +1,3 @@
-<?php
 /*
  *
  * Copyright 2015, Google Inc.
@@ -31,23 +30,22 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  */
-function grpcAutoloader($class) {
-  $prefix = 'Grpc\\';
 
-  $base_dir = __DIR__ . '/Grpc/';
+#include <grpc++/generic_stub.h>
 
-  $len = strlen($prefix);
-  if (strncmp($prefix, $class, $len) !== 0) {
-    return;
-  }
+#include <grpc++/impl/rpc_method.h>
 
-  $relative_class = substr($class, $len);
+namespace grpc {
 
-  $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
-
-  if (file_exists($file)) {
-    include $file;
-  }
+// begin a call to a named method
+std::unique_ptr<GenericClientAsyncReaderWriter> GenericStub::Call(
+    ClientContext* context, const grpc::string& method,
+    CompletionQueue* cq, void* tag) {
+  return std::unique_ptr<GenericClientAsyncReaderWriter>(
+      new GenericClientAsyncReaderWriter(
+          channel_.get(), cq, RpcMethod(method.c_str()), context, tag));
 }
 
-spl_autoload_register('grpcAutoloader');
+
+} // namespace grpc
+
diff --git a/src/cpp/client/insecure_credentials.cc b/src/cpp/client/insecure_credentials.cc
index f3ca430bd4c56348fa9a093478c45158c1b8158f..8945b038dec586dacbb6a1086c82b4be60d31374 100644
--- a/src/cpp/client/insecure_credentials.cc
+++ b/src/cpp/client/insecure_credentials.cc
@@ -31,8 +31,6 @@
  *
  */
 
-#include <string>
-
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
 
diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc
index e3c66376236e7e6c4b0a65073f723704794e25aa..d6f9acc675563c25db05d9543ead6535af615e54 100644
--- a/src/cpp/client/secure_credentials.cc
+++ b/src/cpp/client/secure_credentials.cc
@@ -31,8 +31,6 @@
  *
  */
 
-#include <string>
-
 #include <grpc/grpc_security.h>
 #include <grpc/support/log.h>
 
diff --git a/src/cpp/common/call.cc b/src/cpp/common/call.cc
index 5c26a1ad7c3b6a4dcb98dcc5afb99d0b48387046..1599e5117fd1809714b6b4c8dbe3d54361438913 100644
--- a/src/cpp/common/call.cc
+++ b/src/cpp/common/call.cc
@@ -148,7 +148,7 @@ void FillMetadataMap(grpc_metadata_array* arr,
     // TODO(yangg) handle duplicates?
     metadata->insert(std::pair<grpc::string, grpc::string>(
         arr->metadata[i].key,
-        {arr->metadata[i].value, arr->metadata[i].value_length}));
+        grpc::string(arr->metadata[i].value, arr->metadata[i].value_length)));
   }
   grpc_metadata_array_destroy(arr);
   grpc_metadata_array_init(arr);
@@ -186,6 +186,7 @@ void CallOpBuffer::AddRecvMessage(grpc::protobuf::Message* message) {
 
 void CallOpBuffer::AddRecvMessage(ByteBuffer* message) {
   recv_message_buffer_ = message;
+  recv_message_buffer_->Clear();
 }
 
 void CallOpBuffer::AddClientSendClose() { client_send_close_ = true; }
diff --git a/src/cpp/server/secure_server_credentials.cc b/src/cpp/server/secure_server_credentials.cc
index 88f7a9b1a98d22004a5080e782a9ac4c64bbc125..49d69a3fb9a45e7463b8e2744d4d0c275e6d6f4b 100644
--- a/src/cpp/server/secure_server_credentials.cc
+++ b/src/cpp/server/secure_server_credentials.cc
@@ -59,9 +59,12 @@ class SecureServerCredentials GRPC_FINAL : public ServerCredentials {
 std::shared_ptr<ServerCredentials> SslServerCredentials(
     const SslServerCredentialsOptions& options) {
   std::vector<grpc_ssl_pem_key_cert_pair> pem_key_cert_pairs;
-  for (const auto& key_cert_pair : options.pem_key_cert_pairs) {
-    pem_key_cert_pairs.push_back(
-        {key_cert_pair.private_key.c_str(), key_cert_pair.cert_chain.c_str()});
+  for (auto key_cert_pair = options.pem_key_cert_pairs.begin();
+       key_cert_pair != options.pem_key_cert_pairs.end();
+       key_cert_pair++) {
+    grpc_ssl_pem_key_cert_pair p = {key_cert_pair->private_key.c_str(),
+				    key_cert_pair->cert_chain.c_str()};
+    pem_key_cert_pairs.push_back(p);
   }
   grpc_server_credentials* c_creds = grpc_ssl_server_credentials_create(
       options.pem_root_certs.empty() ? nullptr : options.pem_root_certs.c_str(),
diff --git a/src/cpp/server/server.cc b/src/cpp/server/server.cc
index 9dbf3392d826090a062eab70139348c8baa2f44e..bd0a23739c3128e6b53648db304dfd0337427e4c 100644
--- a/src/cpp/server/server.cc
+++ b/src/cpp/server/server.cc
@@ -248,8 +248,8 @@ bool Server::Start() {
 
   // Start processing rpcs.
   if (!sync_methods_.empty()) {
-    for (auto& m : sync_methods_) {
-      m.Request(server_);
+    for (auto m = sync_methods_.begin(); m != sync_methods_.end(); m++) {
+      m->Request(server_);
     }
 
     ScheduleCallback();
diff --git a/src/cpp/server/server_builder.cc b/src/cpp/server/server_builder.cc
index 58bf9d937f3a72daf9dd765e4e6e3fefa23e603f..c5e115f3967de72fafe8a50060019de3f6f2cc1f 100644
--- a/src/cpp/server/server_builder.cc
+++ b/src/cpp/server/server_builder.cc
@@ -86,24 +86,26 @@ std::unique_ptr<Server> ServerBuilder::BuildAndStart() {
     thread_pool_owned = true;
   }
   std::unique_ptr<Server> server(new Server(thread_pool_, thread_pool_owned));
-  for (auto* service : services_) {
-    if (!server->RegisterService(service)) {
+  for (auto service = services_.begin(); service != services_.end();
+       service++) {
+    if (!server->RegisterService(*service)) {
       return nullptr;
     }
   }
-  for (auto* service : async_services_) {
-    if (!server->RegisterAsyncService(service)) {
+  for (auto service = async_services_.begin();
+       service != async_services_.end(); service++) {
+    if (!server->RegisterAsyncService(*service)) {
       return nullptr;
     }
   }
   if (generic_service_) {
     server->RegisterAsyncGenericService(generic_service_);
   }
-  for (auto& port : ports_) {
-    int r = server->AddListeningPort(port.addr, port.creds.get());
+  for (auto port = ports_.begin(); port != ports_.end(); port++) {
+    int r = server->AddListeningPort(port->addr, port->creds.get());
     if (!r) return nullptr;
-    if (port.selected_port != nullptr) {
-      *port.selected_port = r;
+    if (port->selected_port != nullptr) {
+      *port->selected_port = r;
     }
   }
   if (!server->Start()) {
diff --git a/src/cpp/server/thread_pool.cc b/src/cpp/server/thread_pool.cc
index d3013b806c0c669ee2b586435b1f68a033c57c99..80c96111b1f9c8d697664359672181810e742b07 100644
--- a/src/cpp/server/thread_pool.cc
+++ b/src/cpp/server/thread_pool.cc
@@ -35,28 +35,29 @@
 
 namespace grpc {
 
+void ThreadPool::ThreadFunc() {
+  for (;;) {
+    // Wait until work is available or we are shutting down.
+    std::unique_lock<std::mutex> lock(mu_);
+    if (!shutdown_ && callbacks_.empty()) {
+      cv_.wait(lock);
+    }
+    // Drain callbacks before considering shutdown to ensure all work
+    // gets completed.
+    if (!callbacks_.empty()) {
+      auto cb = callbacks_.front();
+      callbacks_.pop();
+      lock.unlock();
+      cb();
+    } else if (shutdown_) {
+      return;
+    }
+  }
+}
+
 ThreadPool::ThreadPool(int num_threads) : shutdown_(false) {
   for (int i = 0; i < num_threads; i++) {
-    threads_.push_back(std::thread([this]() {
-      for (;;) {
-        // Wait until work is available or we are shutting down.
-        auto have_work = [this]() { return shutdown_ || !callbacks_.empty(); };
-        std::unique_lock<std::mutex> lock(mu_);
-        if (!have_work()) {
-          cv_.wait(lock, have_work);
-        }
-        // Drain callbacks before considering shutdown to ensure all work
-        // gets completed.
-        if (!callbacks_.empty()) {
-          auto cb = callbacks_.front();
-          callbacks_.pop();
-          lock.unlock();
-          cb();
-        } else if (shutdown_) {
-          return;
-        }
-      }
-    }));
+    threads_.push_back(std::thread(&ThreadPool::ThreadFunc, this));
   }
 }
 
@@ -66,8 +67,8 @@ ThreadPool::~ThreadPool() {
     shutdown_ = true;
     cv_.notify_all();
   }
-  for (auto& t : threads_) {
-    t.join();
+  for (auto t = threads_.begin(); t != threads_.end(); t++) {
+    t->join();
   }
 }
 
diff --git a/src/cpp/server/thread_pool.h b/src/cpp/server/thread_pool.h
index 6225d82a0b65e09fc3f44f7b1eb9cf1de2e7b420..41e2009ff1663d3ac76316f340caeff1f7c80d14 100644
--- a/src/cpp/server/thread_pool.h
+++ b/src/cpp/server/thread_pool.h
@@ -58,6 +58,8 @@ class ThreadPool GRPC_FINAL : public ThreadPoolInterface {
   bool shutdown_;
   std::queue<std::function<void()>> callbacks_;
   std::vector<std::thread> threads_;
+
+  void ThreadFunc();
 };
 
 }  // namespace grpc
diff --git a/src/node/examples/qps_test.js b/src/node/examples/qps_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..00293b464ad9b113f05d870010c7c7c5b19d976a
--- /dev/null
+++ b/src/node/examples/qps_test.js
@@ -0,0 +1,136 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * This script runs a QPS test. It sends requests for a specified length of time
+ * with a specified number pending at any one time. It then outputs the measured
+ * QPS. Usage:
+ * node qps_test.js [--concurrent=count] [--time=seconds]
+ * concurrent defaults to 100 and time defaults to 10
+ */
+
+'use strict';
+
+var async = require('async');
+var parseArgs = require('minimist');
+
+var grpc = require('..');
+var testProto = grpc.load(__dirname + '/../interop/test.proto').grpc.testing;
+var interop_server = require('../interop/interop_server.js');
+
+/**
+ * Runs the QPS test. Sends requests constantly for the given number of seconds,
+ * and keeps concurrent_calls requests pending at all times. When the test ends,
+ * the callback is called with the number of calls that completed within the
+ * time limit.
+ * @param {number} concurrent_calls The number of calls to have pending
+ *     simultaneously
+ * @param {number} seconds The number of seconds to run the test for
+ * @param {function(Error, number)} callback Callback for test completion
+ */
+function runTest(concurrent_calls, seconds, callback) {
+  var testServer = interop_server.getServer(0, false);
+  testServer.server.listen();
+  var client = new testProto.TestService('localhost:' + testServer.port);
+
+  var warmup_num = 100;
+
+  /**
+   * Warms up the client to avoid counting startup time in the test result
+   * @param {function(Error)} callback Called when warmup is complete
+   */
+  function warmUp(callback) {
+    var pending = warmup_num;
+    function startCall() {
+      client.emptyCall({}, function(err, resp) {
+        if (err) {
+          callback(err);
+          return;
+        }
+        pending--;
+        if (pending === 0) {
+          callback(null);
+        }
+      });
+    }
+    for (var i = 0; i < warmup_num; i++) {
+      startCall();
+    }
+  }
+  /**
+   * Run the QPS test. Starts concurrent_calls requests, then starts a new
+   * request whenever one completes until time runs out.
+   * @param {function(Error, number)} callback Called when the test is complete.
+   *     The second argument is the number of calls that finished within the
+   *     time limit
+   */
+  function run(callback) {
+    var running = 0;
+    var count = 0;
+    var start = process.hrtime();
+    function responseCallback(err, resp) {
+      if (process.hrtime(start)[0] < seconds) {
+        count += 1;
+        client.emptyCall({}, responseCallback);
+      } else {
+        running -= 1;
+        if (running <= 0) {
+          callback(null, count);
+        }
+      }
+    }
+    for (var i = 0; i < concurrent_calls; i++) {
+      running += 1;
+      client.emptyCall({}, responseCallback);
+    }
+  }
+  async.waterfall([warmUp, run], function(err, count) {
+      testServer.server.shutdown();
+      callback(err, count);
+    });
+}
+
+if (require.main === module) {
+  var argv = parseArgs(process.argv.slice(2), {
+    default: {'concurrent': 100,
+              'time': 10}
+  });
+  runTest(argv.concurrent, argv.time, function(err, count) {
+    if (err) {
+      throw err;
+    }
+    console.log('Concurrent calls:', argv.concurrent);
+    console.log('Time:', argv.time, 'seconds');
+    console.log('QPS:', (count/argv.time));
+  });
+}
diff --git a/src/node/index.js b/src/node/index.js
index ad3dd96af77a1d40c1642d438c4794b98003871e..0b768edc6bc716e0180fd47d05c1f2ff85aa831e 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -56,7 +56,7 @@ function loadObject(value) {
     });
     return result;
   } else if (value.className === 'Service') {
-    return client.makeClientConstructor(value);
+    return client.makeProtobufClientConstructor(value);
   } else if (value.className === 'Message' || value.className === 'Enum') {
     return value.build();
   } else {
@@ -119,7 +119,7 @@ exports.load = load;
 /**
  * See docs for server.makeServerConstructor
  */
-exports.buildServer = server.makeServerConstructor;
+exports.buildServer = server.makeProtobufServerConstructor;
 
 /**
  * Status name to code number mapping
@@ -141,3 +141,7 @@ exports.Credentials = grpc.Credentials;
 exports.ServerCredentials = grpc.ServerCredentials;
 
 exports.getGoogleAuthDelegate = getGoogleAuthDelegate;
+
+exports.makeGenericClientConstructor = client.makeClientConstructor;
+
+exports.makeGenericServerConstructor = server.makeServerConstructor;
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 54b8dbdc9c617258efcb220b8754e4e9c9d4cd77..c46f7d0526104f75b04ffad24307eca10e801037 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -35,9 +35,6 @@
 
 var _ = require('underscore');
 
-var capitalize = require('underscore.string/capitalize');
-var decapitalize = require('underscore.string/decapitalize');
-
 var grpc = require('bindings')('grpc.node');
 
 var common = require('./common.js');
@@ -463,13 +460,18 @@ var requester_makers = {
 };
 
 /**
- * Creates a constructor for clients for the given service
- * @param {ProtoBuf.Reflect.Service} service The service to generate a client
- *     for
+ * Creates a constructor for a client with the given methods. The methods object
+ * maps method name to an object with the following keys:
+ * path: The path on the server for accessing the method. For example, for
+ *     protocol buffers, we use "/service_name/method_name"
+ * requestStream: bool indicating whether the client sends a stream
+ * resonseStream: bool indicating whether the server sends a stream
+ * requestSerialize: function to serialize request objects
+ * responseDeserialize: function to deserialize response objects
+ * @param {Object} methods An object mapping method names to method attributes
  * @return {function(string, Object)} New client constructor
  */
-function makeClientConstructor(service) {
-  var prefix = '/' + common.fullyQualifiedName(service) + '/';
+function makeClientConstructor(methods) {
   /**
    * Create a client with the given methods
    * @constructor
@@ -489,30 +491,41 @@ function makeClientConstructor(service) {
     this.channel = new grpc.Channel(address, options);
   }
 
-  _.each(service.children, function(method) {
+  _.each(methods, function(attrs, name) {
     var method_type;
-    if (method.requestStream) {
-      if (method.responseStream) {
+    if (attrs.requestStream) {
+      if (attrs.responseStream) {
         method_type = 'bidi';
       } else {
         method_type = 'client_stream';
       }
     } else {
-      if (method.responseStream) {
+      if (attrs.responseStream) {
         method_type = 'server_stream';
       } else {
         method_type = 'unary';
       }
     }
-    var serialize = common.serializeCls(method.resolvedRequestType.build());
-    var deserialize = common.deserializeCls(
-        method.resolvedResponseType.build());
-    Client.prototype[decapitalize(method.name)] = requester_makers[method_type](
-        prefix + capitalize(method.name), serialize, deserialize);
-    Client.prototype[decapitalize(method.name)].serialize = serialize;
-    Client.prototype[decapitalize(method.name)].deserialize = deserialize;
+    var serialize = attrs.requestSerialize;
+    var deserialize = attrs.responseDeserialize;
+    Client.prototype[name] = requester_makers[method_type](
+        attrs.path, serialize, deserialize);
+    Client.prototype[name].serialize = serialize;
+    Client.prototype[name].deserialize = deserialize;
   });
 
+  return Client;
+}
+
+/**
+ * Creates a constructor for clients for the given service
+ * @param {ProtoBuf.Reflect.Service} service The service to generate a client
+ *     for
+ * @return {function(string, Object)} New client constructor
+ */
+function makeProtobufClientConstructor(service) {
+  var method_attrs = common.getProtobufServiceAttrs(service);
+  var Client = makeClientConstructor(method_attrs);
   Client.service = service;
 
   return Client;
@@ -520,6 +533,8 @@ function makeClientConstructor(service) {
 
 exports.makeClientConstructor = makeClientConstructor;
 
+exports.makeProtobufClientConstructor = makeProtobufClientConstructor;
+
 /**
  * See docs for client.status
  */
diff --git a/src/node/src/common.js b/src/node/src/common.js
index eec8f0f9878f7f4fa1974d785b0566e55e235e71..55a6b137828a05150e3cdfd41402ea4ae564efab 100644
--- a/src/node/src/common.js
+++ b/src/node/src/common.js
@@ -36,6 +36,7 @@
 var _ = require('underscore');
 
 var capitalize = require('underscore.string/capitalize');
+var decapitalize = require('underscore.string/decapitalize');
 
 /**
  * Get a function that deserializes a specific type of protobuf.
@@ -109,6 +110,26 @@ function wrapIgnoreNull(func) {
   };
 }
 
+/**
+ * Return a map from method names to method attributes for the service.
+ * @param {ProtoBuf.Reflect.Service} service The service to get attributes for
+ * @return {Object} The attributes map
+ */
+function getProtobufServiceAttrs(service) {
+  var prefix = '/' + fullyQualifiedName(service) + '/';
+  return _.object(_.map(service.children, function(method) {
+    return [decapitalize(method.name), {
+      path: prefix + capitalize(method.name),
+      requestStream: method.requestStream,
+      responseStream: method.responseStream,
+      requestSerialize: serializeCls(method.resolvedRequestType.build()),
+      requestDeserialize: deserializeCls(method.resolvedRequestType.build()),
+      responseSerialize: serializeCls(method.resolvedResponseType.build()),
+      responseDeserialize: deserializeCls(method.resolvedResponseType.build())
+    }];
+  }));
+}
+
 /**
  * See docs for deserializeCls
  */
@@ -128,3 +149,5 @@ exports.fullyQualifiedName = fullyQualifiedName;
  * See docs for wrapIgnoreNull
  */
 exports.wrapIgnoreNull = wrapIgnoreNull;
+
+exports.getProtobufServiceAttrs = getProtobufServiceAttrs;
diff --git a/src/node/src/server.js b/src/node/src/server.js
index b72d110666e833518ec60d889a2c6805b8c1a36c..8a26a43606acb5f79e40d145683e18bc5fb6d2e9 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -35,9 +35,6 @@
 
 var _ = require('underscore');
 
-var capitalize = require('underscore.string/capitalize');
-var decapitalize = require('underscore.string/decapitalize');
-
 var grpc = require('bindings')('grpc.node');
 
 var common = require('./common');
@@ -532,26 +529,20 @@ Server.prototype.bind = function(port, creds) {
 };
 
 /**
- * Creates a constructor for servers with a service defined by the methods
- * object. The methods object has string keys and values of this form:
- * {serialize: function, deserialize: function, client_stream: bool,
- *  server_stream: bool}
- * @param {Object} methods Method descriptor for each method the server should
- *     expose
- * @param {string} prefix The prefex to prepend to each method name
- * @return {function(Object, Object)} New server constructor
+ * Create a constructor for servers with services defined by service_attr_map.
+ * That is an object that maps (namespaced) service names to objects that in
+ * turn map method names to objects with the following keys:
+ * path: The path on the server for accessing the method. For example, for
+ *     protocol buffers, we use "/service_name/method_name"
+ * requestStream: bool indicating whether the client sends a stream
+ * resonseStream: bool indicating whether the server sends a stream
+ * requestDeserialize: function to deserialize request objects
+ * responseSerialize: function to serialize response objects
+ * @param {Object} service_attr_map An object mapping service names to method
+ *     attribute map objects
+ * @return {function(Object, function, Object=)} New server constructor
  */
-function makeServerConstructor(services) {
-  var qual_names = [];
-  _.each(services, function(service) {
-    _.each(service.children, function(method) {
-      var name = common.fullyQualifiedName(method);
-      if (_.indexOf(qual_names, name) !== -1) {
-        throw new Error('Method ' + name + ' exposed by more than one service');
-      }
-      qual_names.push(name);
-    });
-  });
+function makeServerConstructor(service_attr_map) {
   /**
    * Create a server with the given handlers for all of the methods.
    * @constructor
@@ -565,41 +556,34 @@ function makeServerConstructor(services) {
   function SurfaceServer(service_handlers, getMetadata, options) {
     var server = new Server(getMetadata, options);
     this.inner_server = server;
-    _.each(services, function(service) {
-      var service_name = common.fullyQualifiedName(service);
+    _.each(service_attr_map, function(service_attrs, service_name) {
       if (service_handlers[service_name] === undefined) {
         throw new Error('Handlers for service ' +
             service_name + ' not provided.');
       }
-      var prefix = '/' + common.fullyQualifiedName(service) + '/';
-      _.each(service.children, function(method) {
+      _.each(service_attrs, function(attrs, name) {
         var method_type;
-        if (method.requestStream) {
-          if (method.responseStream) {
+        if (attrs.requestStream) {
+          if (attrs.responseStream) {
             method_type = 'bidi';
           } else {
             method_type = 'client_stream';
           }
         } else {
-          if (method.responseStream) {
+          if (attrs.responseStream) {
             method_type = 'server_stream';
           } else {
             method_type = 'unary';
           }
         }
-        if (service_handlers[service_name][decapitalize(method.name)] ===
-            undefined) {
-          throw new Error('Method handler for ' +
-              common.fullyQualifiedName(method) + ' not provided.');
+        if (service_handlers[service_name][name] === undefined) {
+          throw new Error('Method handler for ' + attrs.path +
+              ' not provided.');
         }
-        var serialize = common.serializeCls(
-            method.resolvedResponseType.build());
-        var deserialize = common.deserializeCls(
-            method.resolvedRequestType.build());
-        server.register(
-            prefix + capitalize(method.name),
-            service_handlers[service_name][decapitalize(method.name)],
-            serialize, deserialize, method_type);
+        var serialize = attrs.responseSerialize;
+        var deserialize = attrs.requestDeserialize;
+        server.register(attrs.path, service_handlers[service_name][name],
+                        serialize, deserialize, method_type);
       });
     }, this);
   }
@@ -635,7 +619,40 @@ function makeServerConstructor(services) {
   return SurfaceServer;
 }
 
+/**
+ * Create a constructor for servers that serve the given services.
+ * @param {Array<ProtoBuf.Reflect.Service>} services The services that the
+ *     servers will serve
+ * @return {function(Object, function, Object=)} New server constructor
+ */
+function makeProtobufServerConstructor(services) {
+  var qual_names = [];
+  var service_attr_map = {};
+  _.each(services, function(service) {
+    var service_name = common.fullyQualifiedName(service);
+    _.each(service.children, function(method) {
+      var name = common.fullyQualifiedName(method);
+      if (_.indexOf(qual_names, name) !== -1) {
+        throw new Error('Method ' + name + ' exposed by more than one service');
+      }
+      qual_names.push(name);
+    });
+    var method_attrs = common.getProtobufServiceAttrs(service);
+    if (!service_attr_map.hasOwnProperty(service_name)) {
+      service_attr_map[service_name] = {};
+    }
+    service_attr_map[service_name] = _.extend(service_attr_map[service_name],
+                                              method_attrs);
+  });
+  return makeServerConstructor(service_attr_map);
+}
+
 /**
  * See documentation for makeServerConstructor
  */
 exports.makeServerConstructor = makeServerConstructor;
+
+/**
+ * See documentation for makeProtobufServerConstructor
+ */
+exports.makeProtobufServerConstructor = makeProtobufServerConstructor;
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index 91d8197bee1663b2379078e9c1835e0a9adc21d8..96b47815e1fc33a75d4f2937f8e6a3f83b31ec37 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -45,6 +45,8 @@ var math_proto = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto');
 
 var mathService = math_proto.lookup('math.Math');
 
+var capitalize = require('underscore.string/capitalize');
+
 describe('Surface server constructor', function() {
   it('Should fail with conflicting method names', function() {
     assert.throws(function() {
@@ -75,6 +77,55 @@ describe('Surface server constructor', function() {
     }, /math.Math/);
   });
 });
+describe('Generic client and server', function() {
+  function toString(val) {
+    return val.toString();
+  }
+  function toBuffer(str) {
+    return new Buffer(str);
+  }
+  var string_service_attrs = {
+    'capitalize' : {
+      path: '/string/capitalize',
+      requestStream: false,
+      responseStream: false,
+      requestSerialize: toBuffer,
+      requestDeserialize: toString,
+      responseSerialize: toBuffer,
+      responseDeserialize: toString
+    }
+  };
+  describe('String client and server', function() {
+    var client;
+    var server;
+    before(function() {
+      var Server = grpc.makeGenericServerConstructor({
+        string: string_service_attrs
+      });
+      server = new Server({
+        string: {
+          capitalize: function(call, callback) {
+            callback(null, capitalize(call.request));
+          }
+        }
+      });
+      var port = server.bind('localhost:0');
+      server.listen();
+      var Client = grpc.makeGenericClientConstructor(string_service_attrs);
+      client = new Client('localhost:' + port);
+    });
+    after(function() {
+      server.shutdown();
+    });
+    it('Should respond with a capitalized string', function(done) {
+      client.capitalize('abc', function(err, response) {
+        assert.ifError(err);
+        assert.strictEqual(response, 'Abc');
+        done();
+      });
+    });
+  });
+});
 describe('Cancelling surface client', function() {
   var client;
   var server;
@@ -89,7 +140,7 @@ describe('Cancelling surface client', function() {
       }
     });
     var port = server.bind('localhost:0');
-    var Client = surface_client.makeClientConstructor(mathService);
+    var Client = surface_client.makeProtobufClientConstructor(mathService);
     client = new Client('localhost:' + port);
   });
   after(function() {
diff --git a/src/php/composer.json b/src/php/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..dca61a9889aa73463f2d57cd6e92efcad192b8e0
--- /dev/null
+++ b/src/php/composer.json
@@ -0,0 +1,15 @@
+{
+  "name": "grpc/grpc",
+  "description": "gRPC library for PHP",
+  "version": "0.2.0",
+  "homepage": "http://grpc.io",
+  "license": "BSD-3-Clause",
+  "require": {
+    "php": ">=5.5.0"
+  },
+  "autoload": {
+    "psr-4": {
+      "Grpc\\": "lib/Grpc/"
+    }
+  }
+}
diff --git a/src/php/composer.lock b/src/php/composer.lock
new file mode 100644
index 0000000000000000000000000000000000000000..47fc70fd2d1cec4e83a08ca4f11bef5b09f28543
--- /dev/null
+++ b/src/php/composer.lock
@@ -0,0 +1,19 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "hash": "65467a098f5fd8b8fe5f7f6e10226f8a",
+    "packages": [],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.5.0"
+    },
+    "platform-dev": []
+}
diff --git a/src/php/ext/grpc/byte_buffer.c b/src/php/ext/grpc/byte_buffer.c
index 1ced1bf3f033e717a44c6dc6df160074c2c40410..9f122d6da6723ab61691e9709be52c83dad6b9bb 100644
--- a/src/php/ext/grpc/byte_buffer.c
+++ b/src/php/ext/grpc/byte_buffer.c
@@ -57,6 +57,11 @@ grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length) {
 
 void byte_buffer_to_string(grpc_byte_buffer *buffer, char **out_string,
                            size_t *out_length) {
+  if (buffer == NULL) {
+    *out_string = NULL;
+    *out_length = 0;
+    return;
+  }
   size_t length = grpc_byte_buffer_length(buffer);
   char *string = ecalloc(length + 1, sizeof(char));
   size_t offset = 0;
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c
index 798747109a1b21c486a0f374db6e977ae9f22524..ba1b2a407d3027a791bd3097cf6e25b9a41cc08d 100644
--- a/src/php/ext/grpc/call.c
+++ b/src/php/ext/grpc/call.c
@@ -49,11 +49,11 @@
 #include <stdbool.h>
 
 #include "grpc/support/log.h"
+#include "grpc/support/alloc.h"
 #include "grpc/grpc.h"
 
 #include "timeval.h"
 #include "channel.h"
-#include "completion_queue.h"
 #include "byte_buffer.h"
 
 zend_class_entry *grpc_ce_call;
@@ -61,7 +61,19 @@ zend_class_entry *grpc_ce_call;
 /* Frees and destroys an instance of wrapped_grpc_call */
 void free_wrapped_grpc_call(void *object TSRMLS_DC) {
   wrapped_grpc_call *call = (wrapped_grpc_call *)object;
+  grpc_event *event;
   if (call->owned && call->wrapped != NULL) {
+    if (call->queue != NULL) {
+      grpc_completion_queue_shutdown(call->queue);
+      event = grpc_completion_queue_next(call->queue, gpr_inf_future);
+      while (event != NULL) {
+        if (event->type == GRPC_QUEUE_SHUTDOWN) {
+          break;
+        }
+        event = grpc_completion_queue_next(call->queue, gpr_inf_future);
+      }
+      grpc_completion_queue_destroy(call->queue);
+    }
     grpc_call_destroy(call->wrapped);
   }
   efree(call);
@@ -88,17 +100,23 @@ zend_object_value create_wrapped_grpc_call(zend_class_entry *class_type
 
 /* Wraps a grpc_call struct in a PHP object. Owned indicates whether the struct
    should be destroyed at the end of the object's lifecycle */
-zval *grpc_php_wrap_call(grpc_call *wrapped, bool owned) {
+zval *grpc_php_wrap_call(grpc_call *wrapped, grpc_completion_queue *queue,
+                         bool owned) {
   zval *call_object;
   MAKE_STD_ZVAL(call_object);
   object_init_ex(call_object, grpc_ce_call);
   wrapped_grpc_call *call =
       (wrapped_grpc_call *)zend_object_store_get_object(call_object TSRMLS_CC);
   call->wrapped = wrapped;
+  call->queue = queue;
   return call_object;
 }
 
-zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements) {
+/* Creates and returns a PHP array object with the data in a
+ * grpc_metadata_array. Returns NULL on failure */
+zval *grpc_parse_metadata_array(grpc_metadata_array *metadata_array) {
+  int count = metadata_array->count;
+  grpc_metadata *elements = metadata_array->metadata;
   int i;
   zval *array;
   zval **data = NULL;
@@ -139,6 +157,64 @@ zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements) {
   return array;
 }
 
+/* Populates a grpc_metadata_array with the data in a PHP array object.
+   Returns true on success and false on failure */
+bool create_metadata_array(zval *array, grpc_metadata_array *metadata) {
+  zval **inner_array;
+  zval **value;
+  HashTable *array_hash;
+  HashPosition array_pointer;
+  HashTable *inner_array_hash;
+  HashPosition inner_array_pointer;
+  char *key;
+  uint key_len;
+  ulong index;
+  if (Z_TYPE_P(array) != IS_ARRAY) {
+    return false;
+  }
+  grpc_metadata_array_init(metadata);
+  array_hash = Z_ARRVAL_P(array);
+  for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
+       zend_hash_get_current_data_ex(array_hash, (void**)&inner_array,
+                                     &array_pointer) == SUCCESS;
+       zend_hash_move_forward_ex(array_hash, &array_pointer)) {
+    if (zend_hash_get_current_key_ex(array_hash, &key, &key_len, &index, 0,
+                                     &array_pointer) != HASH_KEY_IS_STRING) {
+      return false;
+    }
+    if (Z_TYPE_P(*inner_array) != IS_ARRAY) {
+      return false;
+    }
+    inner_array_hash = Z_ARRVAL_P(*inner_array);
+    metadata->capacity += zend_hash_num_elements(inner_array_hash);
+  }
+  metadata->metadata = gpr_malloc(metadata->capacity * sizeof(grpc_metadata));
+  for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
+       zend_hash_get_current_data_ex(array_hash, (void**)&inner_array,
+                                     &array_pointer) == SUCCESS;
+       zend_hash_move_forward_ex(array_hash, &array_pointer)) {
+    if (zend_hash_get_current_key_ex(array_hash, &key, &key_len, &index, 0,
+                                     &array_pointer) != HASH_KEY_IS_STRING) {
+      return false;
+    }
+    inner_array_hash = Z_ARRVAL_P(*inner_array);
+    for (zend_hash_internal_pointer_reset_ex(inner_array_hash,
+                                             &inner_array_pointer);
+         zend_hash_get_current_data_ex(inner_array_hash, (void**)&value,
+                                       &inner_array_pointer) == SUCCESS;
+         zend_hash_move_forward_ex(inner_array_hash, &inner_array_pointer)) {
+      if (Z_TYPE_P(*value) != IS_STRING) {
+        return false;
+      }
+      metadata->metadata[metadata->count].key = key;
+      metadata->metadata[metadata->count].value = Z_STRVAL_P(*value);
+      metadata->metadata[metadata->count].value_length = Z_STRLEN_P(*value);
+      metadata->count += 1;
+    }
+  }
+  return true;
+}
+
 /**
  * Constructs a new instance of the Call class.
  * @param Channel $channel The channel to associate the call with. Must not be
@@ -157,9 +233,10 @@ PHP_METHOD(Call, __construct) {
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OsO", &channel_obj,
                             grpc_ce_channel, &method, &method_len,
                             &deadline_obj, grpc_ce_timeval) == FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "Call expects a Channel, a String, and a Timeval",
-                         1 TSRMLS_CC);
+    zend_throw_exception(
+        spl_ce_InvalidArgumentException,
+        "Call expects a Channel, a String, and a Timeval",
+        1 TSRMLS_CC);
     return;
   }
   wrapped_grpc_channel *channel =
@@ -175,289 +252,250 @@ PHP_METHOD(Call, __construct) {
   wrapped_grpc_timeval *deadline =
       (wrapped_grpc_timeval *)zend_object_store_get_object(
           deadline_obj TSRMLS_CC);
-  call->wrapped = grpc_channel_create_call_old(
-      channel->wrapped, method, channel->target, deadline->wrapped);
+  call->queue = grpc_completion_queue_create();
+  call->wrapped = grpc_channel_create_call(
+      channel->wrapped, call->queue, method, channel->target,
+      deadline->wrapped);
 }
 
 /**
- * Add metadata to the call. All array keys must be strings. If the value is a
- * string, it is added as a key/value pair. If it is an array, each value is
- * added paired with the same string
- * @param array $metadata The metadata to add
- * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
- * (optional)
- * @return Void
+ * Start a batch of RPC actions.
+ * @param array batch Array of actions to take
+ * @return object Object with results of all actions
  */
-PHP_METHOD(Call, add_metadata) {
+PHP_METHOD(Call, start_batch) {
   wrapped_grpc_call *call =
       (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  grpc_metadata metadata;
-  grpc_call_error error_code;
+  grpc_op ops[8];
+  size_t op_num = 0;
   zval *array;
-  zval **inner_array;
   zval **value;
+  zval **inner_value;
   HashTable *array_hash;
   HashPosition array_pointer;
-  HashTable *inner_array_hash;
-  HashPosition inner_array_pointer;
+  HashTable *status_hash;
   char *key;
   uint key_len;
   ulong index;
-  long flags = 0;
-  /* "a|l" == 1 array, 1 optional long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|l", &array, &flags) ==
+  grpc_metadata_array metadata;
+  grpc_metadata_array trailing_metadata;
+  grpc_metadata_array recv_metadata;
+  grpc_metadata_array recv_trailing_metadata;
+  grpc_status_code status;
+  char *status_details = NULL;
+  size_t status_details_capacity = 0;
+  grpc_byte_buffer *message;
+  int cancelled;
+  grpc_call_error error;
+  grpc_event *event;
+  zval *result;
+  char *message_str;
+  size_t message_len;
+  zval *recv_status;
+  grpc_metadata_array_init(&metadata);
+  grpc_metadata_array_init(&trailing_metadata);
+  grpc_metadata_array_init(&recv_metadata);
+  grpc_metadata_array_init(&recv_trailing_metadata);
+  MAKE_STD_ZVAL(result);
+  object_init(result);
+  /* "a" == 1 array */
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &array) ==
       FAILURE) {
     zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "add_metadata expects an array and an optional long",
-                         1 TSRMLS_CC);
-    return;
+                         "start_batch expects an array", 1 TSRMLS_CC);
+    goto cleanup;
   }
   array_hash = Z_ARRVAL_P(array);
   for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
-       zend_hash_get_current_data_ex(array_hash, (void**)&inner_array,
+       zend_hash_get_current_data_ex(array_hash, (void**)&value,
                                      &array_pointer) == SUCCESS;
        zend_hash_move_forward_ex(array_hash, &array_pointer)) {
     if (zend_hash_get_current_key_ex(array_hash, &key, &key_len, &index, 0,
-                                     &array_pointer) != HASH_KEY_IS_STRING) {
+                                     &array_pointer) != HASH_KEY_IS_LONG) {
       zend_throw_exception(spl_ce_InvalidArgumentException,
-                           "metadata keys must be strings", 1 TSRMLS_CC);
-      return;
+                           "batch keys must be integers", 1 TSRMLS_CC);
+      goto cleanup;
     }
-    if (Z_TYPE_P(*inner_array) != IS_ARRAY) {
-      zend_throw_exception(spl_ce_InvalidArgumentException,
-                           "metadata values must be arrays",
-                           1 TSRMLS_CC);
-      return;
-    }
-    inner_array_hash = Z_ARRVAL_P(*inner_array);
-    for (zend_hash_internal_pointer_reset_ex(inner_array_hash,
-                                             &inner_array_pointer);
-         zend_hash_get_current_data_ex(inner_array_hash, (void**)&value,
-                                       &inner_array_pointer) == SUCCESS;
-         zend_hash_move_forward_ex(inner_array_hash, &inner_array_pointer)) {
-      if (Z_TYPE_P(*value) != IS_STRING) {
+    switch(index) {
+      case GRPC_OP_SEND_INITIAL_METADATA:
+        if (!create_metadata_array(*value, &metadata)) {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "Bad metadata value given", 1 TSRMLS_CC);
+          goto cleanup;
+        }
+        ops[op_num].data.send_initial_metadata.count =
+            metadata.count;
+        ops[op_num].data.send_initial_metadata.metadata =
+            metadata.metadata;
+        break;
+      case GRPC_OP_SEND_MESSAGE:
+        if (Z_TYPE_PP(value) != IS_STRING) {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "Expected a string for send message",
+                               1 TSRMLS_CC);
+        }
+        ops[op_num].data.send_message =
+            string_to_byte_buffer(Z_STRVAL_PP(value), Z_STRLEN_PP(value));
+        break;
+      case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
+        break;
+      case GRPC_OP_SEND_STATUS_FROM_SERVER:
+        status_hash = Z_ARRVAL_PP(value);
+        if (zend_hash_find(status_hash, "metadata", sizeof("metadata"),
+                           (void **)&inner_value) == SUCCESS) {
+          if (!create_metadata_array(*inner_value, &trailing_metadata)) {
+            zend_throw_exception(spl_ce_InvalidArgumentException,
+                                 "Bad trailing metadata value given",
+                                 1 TSRMLS_CC);
+            goto cleanup;
+          }
+          ops[op_num].data.send_status_from_server.trailing_metadata =
+              trailing_metadata.metadata;
+          ops[op_num].data.send_status_from_server.trailing_metadata_count =
+              trailing_metadata.count;
+        }
+        if (zend_hash_find(status_hash, "code", sizeof("code"),
+                           (void**)&inner_value) == SUCCESS) {
+          if (Z_TYPE_PP(inner_value) != IS_LONG) {
+            zend_throw_exception(spl_ce_InvalidArgumentException,
+                                 "Status code must be an integer",
+                                 1 TSRMLS_CC);
+            goto cleanup;
+          }
+          ops[op_num].data.send_status_from_server.status =
+              Z_LVAL_PP(inner_value);
+        } else {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "Integer status code is required",
+                               1 TSRMLS_CC);
+          goto cleanup;
+        }
+        if (zend_hash_find(status_hash, "details", sizeof("details"),
+                           (void**)&inner_value) == SUCCESS) {
+          if (Z_TYPE_PP(inner_value) != IS_STRING) {
+            zend_throw_exception(spl_ce_InvalidArgumentException,
+                                 "Status details must be a string",
+                                 1 TSRMLS_CC);
+            goto cleanup;
+          }
+          ops[op_num].data.send_status_from_server.status_details =
+              Z_STRVAL_PP(inner_value);
+        } else {
+          zend_throw_exception(spl_ce_InvalidArgumentException,
+                               "String status details is required",
+                               1 TSRMLS_CC);
+          goto cleanup;
+        }
+        break;
+      case GRPC_OP_RECV_INITIAL_METADATA:
+        ops[op_num].data.recv_initial_metadata = &recv_metadata;
+        break;
+      case GRPC_OP_RECV_MESSAGE:
+        ops[op_num].data.recv_message = &message;
+        break;
+      case GRPC_OP_RECV_STATUS_ON_CLIENT:
+        ops[op_num].data.recv_status_on_client.trailing_metadata =
+            &recv_trailing_metadata;
+        ops[op_num].data.recv_status_on_client.status = &status;
+        ops[op_num].data.recv_status_on_client.status_details =
+            &status_details;
+        ops[op_num].data.recv_status_on_client.status_details_capacity =
+            &status_details_capacity;
+        break;
+      case GRPC_OP_RECV_CLOSE_ON_SERVER:
+        ops[op_num].data.recv_close_on_server.cancelled = &cancelled;
+        break;
+      default:
         zend_throw_exception(spl_ce_InvalidArgumentException,
-                             "metadata values must be arrays of strings",
-                             1 TSRMLS_CC);
-        return;
-      }
-      metadata.key = key;
-      metadata.value = Z_STRVAL_P(*value);
-      metadata.value_length = Z_STRLEN_P(*value);
-      error_code = grpc_call_add_metadata_old(call->wrapped, &metadata, 0u);
-      MAYBE_THROW_CALL_ERROR(add_metadata, error_code);
+                             "Unrecognized key in batch", 1 TSRMLS_CC);
+        goto cleanup;
     }
+    ops[op_num].op = (grpc_op_type)index;
+    op_num++;
   }
-}
-
-/**
- * Invoke the RPC. Starts sending metadata and request headers over the wire
- * @param CompletionQueue $queue The completion queue to use with this call
- * @param long $metadata_tag The tag to associate with returned metadata
- * @param long $finished_tag The tag to associate with the finished event
- * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
- * (optional)
- * @return Void
- */
-PHP_METHOD(Call, invoke) {
-  grpc_call_error error_code;
-  long tag1;
-  long tag2;
-  zval *queue_obj;
-  long flags = 0;
-  /* "Oll|l" == 1 Object, 3 mandatory longs, 1 optional long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oll|l", &queue_obj,
-                            grpc_ce_completion_queue, &tag1, &tag2,
-                            &flags) == FAILURE) {
-    zend_throw_exception(
-        spl_ce_InvalidArgumentException,
-        "invoke needs a CompletionQueue, 2 longs, and an optional long",
-        1 TSRMLS_CC);
-    return;
+  error = grpc_call_start_batch(call->wrapped, ops, op_num, call->wrapped);
+  if (error != GRPC_CALL_OK) {
+    zend_throw_exception(spl_ce_LogicException,
+                         "start_batch was called incorrectly",
+                         (long)error TSRMLS_CC);
+    goto cleanup;
   }
-  add_property_zval(getThis(), "completion_queue", queue_obj);
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  wrapped_grpc_completion_queue *queue =
-      (wrapped_grpc_completion_queue *)zend_object_store_get_object(
-          queue_obj TSRMLS_CC);
-  error_code = grpc_call_invoke_old(call->wrapped, queue->wrapped, (void *)tag1,
-                                    (void *)tag2, (gpr_uint32)flags);
-  MAYBE_THROW_CALL_ERROR(invoke, error_code);
-}
-
-/**
- * Accept an incoming RPC, binding a completion queue to it. To be called after
- * adding metadata to the call, but before sending messages. Can only be called
- * on the server
- * @param CompletionQueue $queue The completion queue to use with this call
- * @param long $finished_tag The tag to associate with the finished event
- * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
- * (optional)
- * @return Void
- */
-PHP_METHOD(Call, server_accept) {
-  long tag;
-  zval *queue_obj;
-  grpc_call_error error_code;
-  /* "Ol|l" == 1 Object, 1 long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ol", &queue_obj,
-                            grpc_ce_completion_queue, &tag) == FAILURE) {
-    zend_throw_exception(
-        spl_ce_InvalidArgumentException,
-        "server_accept expects a CompletionQueue, a long, and an optional long",
-        1 TSRMLS_CC);
-    return;
-  }
-  add_property_zval(getThis(), "completion_queue", queue_obj);
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  wrapped_grpc_completion_queue *queue =
-      (wrapped_grpc_completion_queue *)zend_object_store_get_object(
-          queue_obj TSRMLS_CC);
-  error_code =
-      grpc_call_server_accept_old(call->wrapped, queue->wrapped, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(server_accept, error_code);
-}
-
-PHP_METHOD(Call, server_end_initial_metadata) {
-  grpc_call_error error_code;
-  long flags = 0;
-  /* "|l" == 1 optional long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags) ==
-      FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "server_end_initial_metadata expects an optional long",
-                         1 TSRMLS_CC);
-  }
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  error_code = grpc_call_server_end_initial_metadata_old(call->wrapped, flags);
-  MAYBE_THROW_CALL_ERROR(server_end_initial_metadata, error_code);
-}
-
-/**
- * Called by clients to cancel an RPC on the server.
- * @return Void
- */
-PHP_METHOD(Call, cancel) {
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  grpc_call_error error_code = grpc_call_cancel(call->wrapped);
-  MAYBE_THROW_CALL_ERROR(cancel, error_code);
-}
-
-/**
- * Queue a byte buffer for writing
- * @param string $buffer The buffer to queue for writing
- * @param long $tag The tag to associate with this write
- * @param long $flags A bitwise combination of the Grpc\WRITE_* constants
- * (optional)
- * @return Void
- */
-PHP_METHOD(Call, start_write) {
-  grpc_call_error error_code;
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  char *buffer;
-  int buffer_len;
-  long tag;
-  long flags = 0;
-  /* "Ol|l" == 1 Object, 1 mandatory long, 1 optional long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|l", &buffer,
-                            &buffer_len, &tag, &flags) == FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "start_write expects a string and an optional long",
+  event = grpc_completion_queue_pluck(call->queue, call->wrapped,
+                                      gpr_inf_future);
+  if (event->data.op_complete != GRPC_OP_OK) {
+    zend_throw_exception(spl_ce_LogicException,
+                         "The batch failed for some reason",
                          1 TSRMLS_CC);
-    return;
+    goto cleanup;
   }
-  error_code = grpc_call_start_write_old(
-      call->wrapped, string_to_byte_buffer(buffer, buffer_len), (void *)tag,
-      (gpr_uint32)flags);
-  MAYBE_THROW_CALL_ERROR(start_write, error_code);
-}
-
-/**
- * Queue a status for writing
- * @param long $status_code The status code to send
- * @param string $status_details The status details to send
- * @param long $tag The tag to associate with this status
- * @return Void
- */
-PHP_METHOD(Call, start_write_status) {
-  grpc_call_error error_code;
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  long status_code;
-  int status_details_length;
-  long tag;
-  char *status_details;
-  /* "lsl" == 1 long, 1 string, 1 long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lsl", &status_code,
-                            &status_details, &status_details_length,
-                            &tag) == FAILURE) {
-    zend_throw_exception(
-        spl_ce_InvalidArgumentException,
-        "start_write_status expects a long, a string, and a long", 1 TSRMLS_CC);
-    return;
+  for (int i = 0; i < op_num; i++) {
+    switch(ops[i].op) {
+      case GRPC_OP_SEND_INITIAL_METADATA:
+        add_property_bool(result, "send_metadata", true);
+        break;
+      case GRPC_OP_SEND_MESSAGE:
+        add_property_bool(result, "send_message", true);
+        break;
+      case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
+        add_property_bool(result, "send_close", true);
+        break;
+      case GRPC_OP_SEND_STATUS_FROM_SERVER:
+        add_property_bool(result, "send_status", true);
+        break;
+      case GRPC_OP_RECV_INITIAL_METADATA:
+        add_property_zval(result, "metadata",
+                          grpc_parse_metadata_array(&recv_metadata));
+        break;
+      case GRPC_OP_RECV_MESSAGE:
+        byte_buffer_to_string(message, &message_str, &message_len);
+        if (message_str == NULL) {
+          add_property_null(result, "message");
+        } else {
+          add_property_stringl(result, "message", message_str, message_len,
+                               false);
+        }
+        break;
+      case GRPC_OP_RECV_STATUS_ON_CLIENT:
+        MAKE_STD_ZVAL(recv_status);
+        object_init(recv_status);
+        add_property_zval(recv_status, "metadata",
+                          grpc_parse_metadata_array(&recv_trailing_metadata));
+        add_property_long(recv_status, "code", status);
+        add_property_string(recv_status, "details", status_details, true);
+        add_property_zval(result, "status", recv_status);
+        break;
+      case GRPC_OP_RECV_CLOSE_ON_SERVER:
+        add_property_bool(result, "cancelled", cancelled);
+        break;
+      default:
+        break;
+    }
   }
-  error_code = grpc_call_start_write_status_old(call->wrapped,
-                                                (grpc_status_code)status_code,
-                                                status_details, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(start_write_status, error_code);
-}
-
-/**
- * Indicate that there are no more messages to send
- * @return Void
- */
-PHP_METHOD(Call, writes_done) {
-  grpc_call_error error_code;
-  wrapped_grpc_call *call =
-      (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  long tag;
-  /* "l" == 1 long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "writes_done expects a long", 1 TSRMLS_CC);
-    return;
+cleanup:
+  grpc_metadata_array_destroy(&metadata);
+  grpc_metadata_array_destroy(&trailing_metadata);
+  grpc_metadata_array_destroy(&recv_metadata);
+  grpc_metadata_array_destroy(&recv_trailing_metadata);
+  if (status_details != NULL) {
+    gpr_free(status_details);
   }
-  error_code = grpc_call_writes_done_old(call->wrapped, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(writes_done, error_code);
+  RETURN_DESTROY_ZVAL(result);
 }
 
 /**
- * Initiate a read on a call. Output event contains a byte buffer with the
- * result of the read
- * @param long $tag The tag to associate with this read
- * @return Void
+ * Cancel the call. This will cause the call to end with STATUS_CANCELLED if it
+ * has not already ended with another status.
  */
-PHP_METHOD(Call, start_read) {
-  grpc_call_error error_code;
+PHP_METHOD(Call, cancel) {
   wrapped_grpc_call *call =
       (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  long tag;
-  /* "l" == 1 long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "start_read expects a long", 1 TSRMLS_CC);
-    return;
-  }
-  error_code = grpc_call_start_read_old(call->wrapped, (void *)tag);
-  MAYBE_THROW_CALL_ERROR(start_read, error_code);
+  grpc_call_cancel(call->wrapped);
 }
 
 static zend_function_entry call_methods[] = {
     PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
-    PHP_ME(Call, server_accept, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, server_end_initial_metadata, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, add_metadata, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, invoke, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, start_read, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, start_write, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, start_write_status, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(Call, writes_done, NULL, ZEND_ACC_PUBLIC) PHP_FE_END};
+    PHP_ME(Call, start_batch, NULL, ZEND_ACC_PUBLIC)
+    PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC) PHP_FE_END};
 
 void grpc_init_call(TSRMLS_D) {
   zend_class_entry ce;
diff --git a/src/php/ext/grpc/call.h b/src/php/ext/grpc/call.h
index bce5d82974f37306fd043c6c3304099c5da535e8..743effe5a1bb76fe0741b4f5fb84bd57c28da364 100644
--- a/src/php/ext/grpc/call.h
+++ b/src/php/ext/grpc/call.h
@@ -45,17 +45,6 @@
 
 #include "grpc/grpc.h"
 
-// Throw an exception if error_code is not OK
-#define MAYBE_THROW_CALL_ERROR(func_name, error_code)            \
-  do {                                                           \
-    if (error_code != GRPC_CALL_OK) {                            \
-      zend_throw_exception(spl_ce_LogicException,                \
-                           #func_name " was called incorrectly", \
-                           (long)error_code TSRMLS_CC);          \
-      return;                                                    \
-    }                                                            \
-  } while (0)
-
 /* Class entry for the Call PHP class */
 extern zend_class_entry *grpc_ce_call;
 
@@ -65,16 +54,18 @@ typedef struct wrapped_grpc_call {
 
   bool owned;
   grpc_call *wrapped;
+  grpc_completion_queue *queue;
 } wrapped_grpc_call;
 
 /* Initializes the Call PHP class */
 void grpc_init_call(TSRMLS_D);
 
 /* Creates a Call object that wraps the given grpc_call struct */
-zval *grpc_php_wrap_call(grpc_call *wrapped, bool owned);
+zval *grpc_php_wrap_call(grpc_call *wrapped, grpc_completion_queue *queue,
+                         bool owned);
 
 /* Creates and returns a PHP associative array of metadata from a C array of
  * call metadata */
-zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements);
+zval *grpc_parse_metadata_array(grpc_metadata_array *metadata_array);
 
 #endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */
diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c
index 5e99332fab81a04fe309db1ad395dbd9ba63d3aa..c96fb128a60fb644f3309d49b0b7932942100a63 100644
--- a/src/php/ext/grpc/channel.c
+++ b/src/php/ext/grpc/channel.c
@@ -51,7 +51,6 @@
 #include "grpc/support/log.h"
 #include "grpc/grpc_security.h"
 
-#include "completion_queue.h"
 #include "server.h"
 #include "credentials.h"
 
@@ -139,6 +138,9 @@ PHP_METHOD(Channel, __construct) {
   HashTable *array_hash;
   zval **creds_obj = NULL;
   wrapped_grpc_credentials *creds = NULL;
+  zval **override_obj;
+  char *override;
+  int override_len;
   /* "s|a" == 1 string, 1 optional array */
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &target,
                             &target_length, &args_array) == FAILURE) {
@@ -146,6 +148,8 @@ PHP_METHOD(Channel, __construct) {
                          "Channel expects a string and an array", 1 TSRMLS_CC);
     return;
   }
+  override = target;
+  override_len = target_length;
   if (args_array == NULL) {
     channel->wrapped = grpc_channel_create(target, NULL);
   } else {
@@ -162,6 +166,19 @@ PHP_METHOD(Channel, __construct) {
           *creds_obj TSRMLS_CC);
       zend_hash_del(array_hash, "credentials", 12);
     }
+    if (zend_hash_find(array_hash, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG,
+                       sizeof(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
+                       (void **)&override_obj) == SUCCESS) {
+      if (Z_TYPE_PP(override_obj) != IS_STRING) {
+        zend_throw_exception(spl_ce_InvalidArgumentException,
+                             GRPC_SSL_TARGET_NAME_OVERRIDE_ARG
+                             " must be a string",
+                             1 TSRMLS_CC);
+        return;
+      }
+      override = Z_STRVAL_PP(override_obj);
+      override_len = Z_STRLEN_PP(override_obj);
+    }
     php_grpc_read_args_array(args_array, &args);
     if (creds == NULL) {
       channel->wrapped = grpc_channel_create(target, &args);
@@ -172,8 +189,8 @@ PHP_METHOD(Channel, __construct) {
     }
     efree(args.args);
   }
-  channel->target = ecalloc(target_length + 1, sizeof(char));
-  memcpy(channel->target, target, target_length);
+  channel->target = ecalloc(override_len + 1, sizeof(char));
+  memcpy(channel->target, override, override_len);
 }
 
 /**
diff --git a/src/php/ext/grpc/completion_queue.c b/src/php/ext/grpc/completion_queue.c
deleted file mode 100644
index 93abf5df36bff2b7da199fdc9b964ff5fff27daf..0000000000000000000000000000000000000000
--- a/src/php/ext/grpc/completion_queue.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#include "completion_queue.h"
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "php.h"
-#include "php_ini.h"
-#include "ext/standard/info.h"
-#include "ext/spl/spl_exceptions.h"
-#include "php_grpc.h"
-
-#include "zend_exceptions.h"
-
-#include <stdbool.h>
-
-#include "grpc/grpc.h"
-
-#include "event.h"
-#include "timeval.h"
-
-zend_class_entry *grpc_ce_completion_queue;
-
-/* Frees and destroys a wrapped instance of grpc_completion_queue */
-void free_wrapped_grpc_completion_queue(void *object TSRMLS_DC) {
-  wrapped_grpc_completion_queue *queue = NULL;
-  grpc_event *event;
-  queue = (wrapped_grpc_completion_queue *)object;
-  if (queue->wrapped != NULL) {
-    grpc_completion_queue_shutdown(queue->wrapped);
-    event = grpc_completion_queue_next(queue->wrapped, gpr_inf_future);
-    while (event != NULL) {
-      if (event->type == GRPC_QUEUE_SHUTDOWN) {
-        break;
-      }
-      event = grpc_completion_queue_next(queue->wrapped, gpr_inf_future);
-    }
-    grpc_completion_queue_destroy(queue->wrapped);
-  }
-  efree(queue);
-}
-
-/* Initializes an instance of wrapped_grpc_channel to be associated with an
- * object of a class specified by class_type */
-zend_object_value create_wrapped_grpc_completion_queue(
-    zend_class_entry *class_type TSRMLS_DC) {
-  zend_object_value retval;
-  wrapped_grpc_completion_queue *intern;
-
-  intern = (wrapped_grpc_completion_queue *)emalloc(
-      sizeof(wrapped_grpc_completion_queue));
-  memset(intern, 0, sizeof(wrapped_grpc_completion_queue));
-
-  zend_object_std_init(&intern->std, class_type TSRMLS_CC);
-  object_properties_init(&intern->std, class_type);
-  retval.handle = zend_objects_store_put(
-      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,
-      free_wrapped_grpc_completion_queue, NULL TSRMLS_CC);
-  retval.handlers = zend_get_std_object_handlers();
-  return retval;
-}
-
-/**
- * Construct an instance of CompletionQueue
- */
-PHP_METHOD(CompletionQueue, __construct) {
-  wrapped_grpc_completion_queue *queue =
-      (wrapped_grpc_completion_queue *)zend_object_store_get_object(getThis()
-                                                                    TSRMLS_CC);
-  queue->wrapped = grpc_completion_queue_create();
-}
-
-/**
- * Blocks until an event is available, the completion queue is being shutdown,
- * or timeout is reached. Returns NULL on timeout, otherwise the event that
- * occurred. Callers should call event.finish once they have processed the
- * event.
- * @param Timeval $timeout The timeout for the event
- * @return Event The event that occurred
- */
-PHP_METHOD(CompletionQueue, next) {
-  zval *timeout;
-  /* "O" == 1 Object */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &timeout,
-                            grpc_ce_timeval) == FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "next needs a Timeval", 1 TSRMLS_CC);
-    return;
-  }
-  wrapped_grpc_completion_queue *completion_queue =
-      (wrapped_grpc_completion_queue *)zend_object_store_get_object(getThis()
-                                                                    TSRMLS_CC);
-  wrapped_grpc_timeval *wrapped_timeout =
-      (wrapped_grpc_timeval *)zend_object_store_get_object(timeout TSRMLS_CC);
-  grpc_event *event = grpc_completion_queue_next(completion_queue->wrapped,
-                                                 wrapped_timeout->wrapped);
-  if (event == NULL) {
-    RETURN_NULL();
-  }
-  zval *wrapped_event = grpc_php_convert_event(event);
-  RETURN_DESTROY_ZVAL(wrapped_event);
-}
-
-PHP_METHOD(CompletionQueue, pluck) {
-  long tag;
-  zval *timeout;
-  /* "lO" == 1 long, 1 Object */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO", &tag, &timeout,
-                            grpc_ce_timeval) == FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "pluck needs a long and a Timeval", 1 TSRMLS_CC);
-  }
-  wrapped_grpc_completion_queue *completion_queue =
-      (wrapped_grpc_completion_queue *)zend_object_store_get_object(getThis()
-                                                                    TSRMLS_CC);
-  wrapped_grpc_timeval *wrapped_timeout =
-      (wrapped_grpc_timeval *)zend_object_store_get_object(timeout TSRMLS_CC);
-  grpc_event *event = grpc_completion_queue_pluck(
-      completion_queue->wrapped, (void *)tag, wrapped_timeout->wrapped);
-  if (event == NULL) {
-    RETURN_NULL();
-  }
-  zval *wrapped_event = grpc_php_convert_event(event);
-  RETURN_DESTROY_ZVAL(wrapped_event);
-}
-
-static zend_function_entry completion_queue_methods[] = {
-    PHP_ME(CompletionQueue, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
-    PHP_ME(CompletionQueue, next, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(CompletionQueue, pluck, NULL, ZEND_ACC_PUBLIC) PHP_FE_END};
-
-void grpc_init_completion_queue(TSRMLS_D) {
-  zend_class_entry ce;
-  INIT_CLASS_ENTRY(ce, "Grpc\\CompletionQueue", completion_queue_methods);
-  ce.create_object = create_wrapped_grpc_completion_queue;
-  grpc_ce_completion_queue = zend_register_internal_class(&ce TSRMLS_CC);
-}
diff --git a/src/php/ext/grpc/completion_queue.h b/src/php/ext/grpc/completion_queue.h
deleted file mode 100755
index 1d386cc58f4a9fa8356dbe5b270d1718bc8b6da4..0000000000000000000000000000000000000000
--- a/src/php/ext/grpc/completion_queue.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#ifndef NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_
-#define NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "php.h"
-#include "php_ini.h"
-#include "ext/standard/info.h"
-#include "php_grpc.h"
-
-#include "grpc/grpc.h"
-
-/* Class entry for the PHP CompletionQueue class */
-extern zend_class_entry *grpc_ce_completion_queue;
-
-/* Wrapper class for grpc_completion_queue that can be associated with a
-   PHP object */
-typedef struct wrapped_grpc_completion_queue {
-  zend_object std;
-
-  grpc_completion_queue *wrapped;
-} wrapped_grpc_completion_queue;
-
-/* Initialize the CompletionQueue class */
-void grpc_init_completion_queue(TSRMLS_D);
-
-#endif /* NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_ */
diff --git a/src/php/ext/grpc/config.m4 b/src/php/ext/grpc/config.m4
index 27c67781e78b6252a79c5579345d6bfe9abf2a6a..d1a8decb73afbe2e781748ccce21782f224a9112 100755
--- a/src/php/ext/grpc/config.m4
+++ b/src/php/ext/grpc/config.m4
@@ -66,5 +66,5 @@ if test "$PHP_GRPC" != "no"; then
 
   PHP_SUBST(GRPC_SHARED_LIBADD)
 
-  PHP_NEW_EXTENSION(grpc, byte_buffer.c call.c channel.c completion_queue.c credentials.c event.c timeval.c server.c server_credentials.c php_grpc.c, $ext_shared, , -Wall -Werror -pedantic -std=c99)
+  PHP_NEW_EXTENSION(grpc, byte_buffer.c call.c channel.c credentials.c timeval.c server.c server_credentials.c php_grpc.c, $ext_shared, , -Wall -Werror -pedantic -std=c99)
 fi
diff --git a/src/php/ext/grpc/event.c b/src/php/ext/grpc/event.c
deleted file mode 100644
index 452c4b8bcbafdb57af19804a8f77d0fdf147144a..0000000000000000000000000000000000000000
--- a/src/php/ext/grpc/event.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#include "event.h"
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "php.h"
-#include "php_ini.h"
-#include "ext/standard/info.h"
-#include "php_grpc.h"
-
-#include <stdbool.h>
-
-#include "grpc/grpc.h"
-
-#include "byte_buffer.h"
-#include "call.h"
-#include "timeval.h"
-
-/* Create a new PHP object containing the event data in the event struct.
-   event must not be used after this function is called */
-zval *grpc_php_convert_event(grpc_event *event) {
-  zval *data_object;
-  char *detail_string;
-  size_t detail_len;
-  char *method_string;
-  size_t method_len;
-  char *host_string;
-  size_t host_len;
-  char *read_string;
-  size_t read_len;
-
-  zval *event_object;
-
-  if (event == NULL) {
-    return NULL;
-  }
-
-  MAKE_STD_ZVAL(event_object);
-  object_init(event_object);
-
-  add_property_zval(
-      event_object, "call",
-      grpc_php_wrap_call(event->call, event->type == GRPC_SERVER_RPC_NEW));
-  add_property_long(event_object, "type", event->type);
-  add_property_long(event_object, "tag", (long)event->tag);
-
-  switch (event->type) {
-    case GRPC_QUEUE_SHUTDOWN:
-      add_property_null(event_object, "data");
-      break;
-    case GRPC_READ:
-      if (event->data.read == NULL) {
-        add_property_null(event_object, "data");
-      } else {
-        byte_buffer_to_string(event->data.read, &read_string, &read_len);
-        add_property_stringl(event_object, "data", read_string, read_len, true);
-      }
-      break;
-    case GRPC_WRITE_ACCEPTED:
-      add_property_long(event_object, "data", (long)event->data.write_accepted);
-      break;
-    case GRPC_FINISH_ACCEPTED:
-      add_property_long(event_object, "data",
-                        (long)event->data.finish_accepted);
-      break;
-    case GRPC_CLIENT_METADATA_READ:
-      data_object = grpc_call_create_metadata_array(
-          event->data.client_metadata_read.count,
-          event->data.client_metadata_read.elements);
-      add_property_zval(event_object, "data", data_object);
-      break;
-    case GRPC_FINISHED:
-      MAKE_STD_ZVAL(data_object);
-      object_init(data_object);
-      add_property_long(data_object, "code", event->data.finished.status);
-      if (event->data.finished.details == NULL) {
-        add_property_null(data_object, "details");
-      } else {
-        detail_len = strlen(event->data.finished.details);
-        detail_string = ecalloc(detail_len + 1, sizeof(char));
-        memcpy(detail_string, event->data.finished.details, detail_len);
-        add_property_string(data_object, "details", detail_string, true);
-      }
-      add_property_zval(data_object, "metadata",
-                        grpc_call_create_metadata_array(
-                            event->data.finished.metadata_count,
-                            event->data.finished.metadata_elements));
-      add_property_zval(event_object, "data", data_object);
-      break;
-    case GRPC_SERVER_RPC_NEW:
-      MAKE_STD_ZVAL(data_object);
-      object_init(data_object);
-      method_len = strlen(event->data.server_rpc_new.method);
-      method_string = ecalloc(method_len + 1, sizeof(char));
-      memcpy(method_string, event->data.server_rpc_new.method, method_len);
-      add_property_string(data_object, "method", method_string, false);
-      host_len = strlen(event->data.server_rpc_new.host);
-      host_string = ecalloc(host_len + 1, sizeof(char));
-      memcpy(host_string, event->data.server_rpc_new.host, host_len);
-      add_property_string(data_object, "host", host_string, false);
-      add_property_zval(
-          data_object, "absolute_timeout",
-          grpc_php_wrap_timeval(event->data.server_rpc_new.deadline));
-      add_property_zval(data_object, "metadata",
-                        grpc_call_create_metadata_array(
-                            event->data.server_rpc_new.metadata_count,
-                            event->data.server_rpc_new.metadata_elements));
-      add_property_zval(event_object, "data", data_object);
-      break;
-    default:
-      add_property_null(event_object, "data");
-      break;
-  }
-  grpc_event_finish(event);
-  return event_object;
-}
diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c
index 67e366c385dc0b66f9ef094e0abbccf562362417..1f9edfe881a21d47bf974c483d0cd55e6107bbe5 100644
--- a/src/php/ext/grpc/php_grpc.c
+++ b/src/php/ext/grpc/php_grpc.c
@@ -34,8 +34,6 @@
 #include "call.h"
 #include "channel.h"
 #include "server.h"
-#include "completion_queue.h"
-#include "event.h"
 #include "timeval.h"
 #include "credentials.h"
 #include "server_credentials.h"
@@ -127,27 +125,12 @@ PHP_MINIT_FUNCTION(grpc) {
   REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_INVALID_FLAGS",
                          GRPC_CALL_ERROR_INVALID_FLAGS, CONST_CS);
 
-  /* Register op error constants */
-  REGISTER_LONG_CONSTANT("Grpc\\OP_OK", GRPC_OP_OK, CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\OP_ERROR", GRPC_OP_ERROR, CONST_CS);
-
   /* Register flag constants */
   REGISTER_LONG_CONSTANT("Grpc\\WRITE_BUFFER_HINT", GRPC_WRITE_BUFFER_HINT,
                          CONST_CS);
   REGISTER_LONG_CONSTANT("Grpc\\WRITE_NO_COMPRESS", GRPC_WRITE_NO_COMPRESS,
                          CONST_CS);
 
-  /* Register completion type constants */
-  REGISTER_LONG_CONSTANT("Grpc\\QUEUE_SHUTDOWN", GRPC_QUEUE_SHUTDOWN, CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\READ", GRPC_READ, CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\FINISH_ACCEPTED", GRPC_FINISH_ACCEPTED,
-                         CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\WRITE_ACCEPTED", GRPC_WRITE_ACCEPTED, CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\CLIENT_METADATA_READ",
-                         GRPC_CLIENT_METADATA_READ, CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\FINISHED", GRPC_FINISHED, CONST_CS);
-  REGISTER_LONG_CONSTANT("Grpc\\SERVER_RPC_NEW", GRPC_SERVER_RPC_NEW, CONST_CS);
-
   /* Register status constants */
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_OK", GRPC_STATUS_OK, CONST_CS);
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_CANCELLED", GRPC_STATUS_CANCELLED,
@@ -181,10 +164,27 @@ PHP_MINIT_FUNCTION(grpc) {
   REGISTER_LONG_CONSTANT("Grpc\\STATUS_DATA_LOSS", GRPC_STATUS_DATA_LOSS,
                          CONST_CS);
 
+  /* Register op type constants */
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_INITIAL_METADATA",
+                         GRPC_OP_SEND_INITIAL_METADATA, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_MESSAGE",
+                         GRPC_OP_SEND_MESSAGE, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_CLOSE_FROM_CLIENT",
+                         GRPC_OP_SEND_CLOSE_FROM_CLIENT, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_STATUS_FROM_SERVER",
+                         GRPC_OP_SEND_STATUS_FROM_SERVER, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_INITIAL_METADATA",
+                         GRPC_OP_RECV_INITIAL_METADATA, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_MESSAGE",
+                         GRPC_OP_RECV_MESSAGE, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_STATUS_ON_CLIENT",
+                         GRPC_OP_RECV_STATUS_ON_CLIENT, CONST_CS);
+  REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_CLOSE_ON_SERVER",
+                         GRPC_OP_RECV_CLOSE_ON_SERVER, CONST_CS);
+
   grpc_init_call(TSRMLS_C);
   grpc_init_channel(TSRMLS_C);
   grpc_init_server(TSRMLS_C);
-  grpc_init_completion_queue(TSRMLS_C);
   grpc_init_timeval(TSRMLS_C);
   grpc_init_credentials(TSRMLS_C);
   grpc_init_server_credentials(TSRMLS_C);
diff --git a/src/php/ext/grpc/server.c b/src/php/ext/grpc/server.c
index a5cfd952871798c0281f7767d00a624ca10ed6f4..86b29958fbf59c34fa76b40400e29ae568d23602 100644
--- a/src/php/ext/grpc/server.c
+++ b/src/php/ext/grpc/server.c
@@ -52,15 +52,27 @@
 #include "grpc/grpc_security.h"
 
 #include "server.h"
-#include "completion_queue.h"
 #include "channel.h"
 #include "server_credentials.h"
+#include "timeval.h"
 
 zend_class_entry *grpc_ce_server;
 
 /* Frees and destroys an instance of wrapped_grpc_server */
 void free_wrapped_grpc_server(void *object TSRMLS_DC) {
   wrapped_grpc_server *server = (wrapped_grpc_server *)object;
+  grpc_event *event;
+  if (server->queue != NULL) {
+    grpc_completion_queue_shutdown(server->queue);
+    event = grpc_completion_queue_next(server->queue, gpr_inf_future);
+    while (event != NULL) {
+      if (event->type == GRPC_QUEUE_SHUTDOWN) {
+        break;
+      }
+      event = grpc_completion_queue_next(server->queue, gpr_inf_future);
+    }
+    grpc_completion_queue_destroy(server->queue);
+  }
   if (server->wrapped != NULL) {
     grpc_server_shutdown(server->wrapped);
     grpc_server_destroy(server->wrapped);
@@ -95,26 +107,22 @@ zend_object_value create_wrapped_grpc_server(zend_class_entry *class_type
 PHP_METHOD(Server, __construct) {
   wrapped_grpc_server *server =
       (wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  zval *queue_obj;
   zval *args_array = NULL;
   grpc_channel_args args;
-  /* "O|a" == 1 Object, 1 optional array */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|a", &queue_obj,
-                            grpc_ce_completion_queue, &args_array) == FAILURE) {
+  /* "|a" == 1 optional array */
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a", &args_array) ==
+      FAILURE) {
     zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "Server expects a CompletionQueue and an array",
+                         "Server expects an array",
                          1 TSRMLS_CC);
     return;
   }
-  add_property_zval(getThis(), "completion_queue", queue_obj);
-  wrapped_grpc_completion_queue *queue =
-      (wrapped_grpc_completion_queue *)zend_object_store_get_object(
-          queue_obj TSRMLS_CC);
+  server->queue = grpc_completion_queue_create();
   if (args_array == NULL) {
-    server->wrapped = grpc_server_create(queue->wrapped, NULL);
+    server->wrapped = grpc_server_create(server->queue, NULL);
   } else {
     php_grpc_read_args_array(args_array, &args);
-    server->wrapped = grpc_server_create(queue->wrapped, &args);
+    server->wrapped = grpc_server_create(server->queue, &args);
     efree(args.args);
   }
 }
@@ -129,16 +137,40 @@ PHP_METHOD(Server, request_call) {
   grpc_call_error error_code;
   wrapped_grpc_server *server =
       (wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
-  long tag_new;
-  /* "l" == 1 long */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag_new) ==
-      FAILURE) {
-    zend_throw_exception(spl_ce_InvalidArgumentException,
-                         "request_call expects a long", 1 TSRMLS_CC);
-    return;
+  grpc_call *call;
+  grpc_call_details details;
+  grpc_metadata_array metadata;
+  zval *result;
+  grpc_event *event;
+  MAKE_STD_ZVAL(result);
+  object_init(result);
+  grpc_call_details_init(&details);
+  grpc_metadata_array_init(&metadata);
+  error_code = grpc_server_request_call(server->wrapped, &call, &details,
+                                        &metadata, server->queue, NULL);
+  if (error_code != GRPC_CALL_OK) {
+    zend_throw_exception(spl_ce_LogicException, "request_call failed",
+                         (long)error_code TSRMLS_CC);
+    goto cleanup;
+  }
+  event = grpc_completion_queue_pluck(server->queue, NULL, gpr_inf_future);
+  if (event->data.op_complete != GRPC_OP_OK) {
+    zend_throw_exception(spl_ce_LogicException,
+                         "Failed to request a call for some reason",
+                         1 TSRMLS_CC);
+    goto cleanup;
   }
-  error_code = grpc_server_request_call_old(server->wrapped, (void *)tag_new);
-  MAYBE_THROW_CALL_ERROR(request_call, error_code);
+  add_property_zval(result, "call", grpc_php_wrap_call(call, server->queue,
+                                                       true));
+  add_property_string(result, "method", details.method, true);
+  add_property_string(result, "host", details.host, true);
+  add_property_zval(result, "absolute_deadline",
+                    grpc_php_wrap_timeval(details.deadline));
+  add_property_zval(result, "metadata", grpc_parse_metadata_array(&metadata));
+cleanup:
+  grpc_call_details_destroy(&details);
+  grpc_metadata_array_destroy(&metadata);
+  RETURN_DESTROY_ZVAL(result);
 }
 
 /**
@@ -168,7 +200,7 @@ PHP_METHOD(Server, add_secure_http2_port) {
   int addr_len;
   zval *creds_obj;
   /* "sO" == 1 string, 1 object */
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &addr, &addr_len,
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sO", &addr, &addr_len,
                             &creds_obj, grpc_ce_server_credentials) ==
       FAILURE) {
     zend_throw_exception(
diff --git a/src/php/ext/grpc/server.h b/src/php/ext/grpc/server.h
index b55689c5816b3683cefe715626e614e2b477d08b..ebb8d25ae1acd2b3c8023977ea220989576a39ef 100755
--- a/src/php/ext/grpc/server.h
+++ b/src/php/ext/grpc/server.h
@@ -53,6 +53,7 @@ typedef struct wrapped_grpc_server {
   zend_object std;
 
   grpc_server *wrapped;
+  grpc_completion_queue *queue;
 } wrapped_grpc_server;
 
 /* Initializes the Server class */
diff --git a/src/php/lib/Grpc/AbstractCall.php b/src/php/lib/Grpc/AbstractCall.php
new file mode 100644
index 0000000000000000000000000000000000000000..d81df9706711862aae5e3256a9f52ed61ee90edc
--- /dev/null
+++ b/src/php/lib/Grpc/AbstractCall.php
@@ -0,0 +1,77 @@
+<?php
+/*
+ *
+ * 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.
+ *
+ */
+namespace Grpc;
+
+abstract class AbstractCall {
+
+  protected $call;
+  protected $deserialize;
+  protected $metadata;
+
+  /**
+   * Create a new Call wrapper object.
+   * @param Channel $channel The channel to communicate on
+   * @param string $method The method to call on the remote server
+   */
+  public function __construct(Channel $channel, $method, $deserialize) {
+    $this->call = new Call($channel, $method, Timeval::inf_future());
+    $this->deserialize = $deserialize;
+  }
+
+  /**
+   * @return The metadata sent by the server.
+   */
+  public function getMetadata() {
+    return $this->metadata;
+  }
+
+  /**
+   * Cancels the call
+   */
+  public function cancel() {
+    $this->call->cancel();
+  }
+
+  /**
+   * Deserialize a response value to an object.
+   * @param string $value The binary value to deserialize
+   * @return The deserialized value
+   */
+  protected function deserializeResponse($value) {
+    if ($value === null) {
+      return null;
+    }
+    return call_user_func($this->deserialize, $value);
+  }
+}
\ No newline at end of file
diff --git a/src/php/lib/Grpc/AbstractSurfaceActiveCall.php b/src/php/lib/Grpc/AbstractSurfaceActiveCall.php
deleted file mode 100755
index 9d0af090ceb0f98822263b8565adb1fdc95e082a..0000000000000000000000000000000000000000
--- a/src/php/lib/Grpc/AbstractSurfaceActiveCall.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-
-/*
- *
- * 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.
- *
- */
-
-namespace Grpc;
-
-require_once realpath(dirname(__FILE__) . '/../autoload.php');
-
-/**
- * Represents an active call that allows sending and recieving messages.
- * Subclasses restrict how data can be sent and recieved.
- */
-abstract class AbstractSurfaceActiveCall {
-  private $active_call;
-  private $deserialize;
-
-  /**
-   * Create a new surface active call.
-   * @param Channel $channel The channel to communicate on
-   * @param string $method The method to call on the remote server
-   * @param callable $deserialize The function to deserialize a value
-   * @param array $metadata Metadata to send with the call, if applicable
-   * @param long $flags Write flags to use with this call
-   */
-  public function __construct(Channel $channel,
-                       $method,
-                       callable $deserialize,
-                       $metadata = array(),
-                       $flags = 0) {
-    $this->active_call = new ActiveCall($channel, $method, $metadata, $flags);
-    $this->deserialize = $deserialize;
-  }
-
-  /**
-   * @return The metadata sent by the server
-   */
-  public function getMetadata() {
-    return $this->metadata();
-  }
-
-  /**
-   * Cancels the call
-   */
-  public function cancel() {
-    $this->active_call->cancel();
-  }
-
-  protected function _read() {
-    $response = $this->active_call->read();
-    if ($response === null) {
-      return null;
-    }
-    return call_user_func($this->deserialize, $response);
-  }
-
-  protected function _write($value) {
-    return $this->active_call->write($value->serialize());
-  }
-
-  protected function _writesDone() {
-    $this->active_call->writesDone();
-  }
-
-  protected function _getStatus() {
-    return $this->active_call->getStatus();
-  }
-}
diff --git a/src/php/lib/Grpc/ActiveCall.php b/src/php/lib/Grpc/ActiveCall.php
deleted file mode 100755
index f0d0d55582502aa6f06d67d41300d79791d54dc7..0000000000000000000000000000000000000000
--- a/src/php/lib/Grpc/ActiveCall.php
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-/*
- *
- * 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.
- *
- */
-namespace Grpc;
-require_once realpath(dirname(__FILE__) . '/../autoload.php');
-
-/**
- * Represents an active call that allows sending and recieving binary data
- */
-class ActiveCall {
-  private $completion_queue;
-  private $call;
-  private $flags;
-  private $metadata;
-
-  /**
-   * Create a new active call.
-   * @param Channel $channel The channel to communicate on
-   * @param string $method The method to call on the remote server
-   * @param array $metadata Metadata to send with the call, if applicable
-   * @param long $flags Write flags to use with this call
-   */
-  public function __construct(Channel $channel,
-                              $method,
-                              $metadata = array(),
-                              $flags = 0) {
-    $this->completion_queue = new CompletionQueue();
-    $this->call = new Call($channel, $method, Timeval::inf_future());
-    $this->call->add_metadata($metadata, 0);
-    $this->flags = $flags;
-
-    // Invoke the call.
-    $this->call->invoke($this->completion_queue,
-                        CLIENT_METADATA_READ,
-                        FINISHED, 0);
-    $metadata_event = $this->completion_queue->pluck(CLIENT_METADATA_READ,
-                                                     Timeval::inf_future());
-    $this->metadata = $metadata_event->data;
-  }
-
-  /**
-   * @return The metadata sent by the server.
-   */
-  public function getMetadata() {
-    return $this->metadata;
-  }
-
-  /**
-   * Cancels the call
-   */
-  public function cancel() {
-    $this->call->cancel();
-  }
-
-  /**
-   * Read a single message from the server.
-   * @return The next message from the server, or null if there is none.
-   */
-  public function read() {
-    $this->call->start_read(READ);
-    $read_event = $this->completion_queue->pluck(READ, Timeval::inf_future());
-    return $read_event->data;
-  }
-
-  /**
-   * Write a single message to the server. This cannot be called after
-   * writesDone is called.
-   * @param ByteBuffer $data The data to write
-   */
-  public function write($data) {
-    $this->call->start_write($data, WRITE_ACCEPTED, $this->flags);
-    $this->completion_queue->pluck(WRITE_ACCEPTED, Timeval::inf_future());
-  }
-
-  /**
-   * Indicate that no more writes will be sent.
-   */
-  public function writesDone() {
-    $this->call->writes_done(FINISH_ACCEPTED);
-    $this->completion_queue->pluck(FINISH_ACCEPTED, Timeval::inf_future());
-  }
-
-  /**
-   * Wait for the server to send the status, and return it.
-   * @return object The status object, with integer $code, string $details,
-   *     and array $metadata members
-   */
-  public function getStatus() {
-    $status_event = $this->completion_queue->pluck(FINISHED,
-                                                   Timeval::inf_future());
-    return $status_event->data;
-  }
-}
diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php
index fde055a3b327a979e3bff0ef443b052fca5617d1..fc83dace20552b7838487a1b3688e2d210068136 100755
--- a/src/php/lib/Grpc/BaseStub.php
+++ b/src/php/lib/Grpc/BaseStub.php
@@ -32,7 +32,6 @@
  *
  */
 namespace Grpc;
-require_once realpath(dirname(__FILE__) . '/../autoload.php');
 
 /**
  * Base class for generated client stubs. Stub methods are expected to call
@@ -69,11 +68,9 @@ class BaseStub {
                                  $argument,
                                  callable $deserialize,
                                  $metadata = array()) {
-    return new SimpleSurfaceActiveCall($this->channel,
-                                       $method,
-                                       $deserialize,
-                                       $argument,
-                                       $metadata);
+    $call = new UnaryCall($this->channel, $method, $deserialize);
+    $call->start($argument, $metadata);
+    return $call;
   }
 
   /**
@@ -91,11 +88,9 @@ class BaseStub {
                                        $arguments,
                                        callable $deserialize,
                                        $metadata = array()) {
-    return new ClientStreamingSurfaceActiveCall($this->channel,
-                                                $method,
-                                                $deserialize,
-                                                $arguments,
-                                                $metadata);
+    $call = new ClientStreamingCall($this->channel, $method, $deserialize);
+    $call->start($arguments, $metadata);
+    return $call;
   }
 
   /**
@@ -112,11 +107,9 @@ class BaseStub {
                                        $argument,
                                        callable $deserialize,
                                        $metadata = array()) {
-    return new ServerStreamingSurfaceActiveCall($this->channel,
-                                                $method,
-                                                $deserialize,
-                                                $argument,
-                                                $metadata);
+    $call = new ServerStreamingCall($this->channel, $method, $deserialize);
+    $call->start($argument, $metadata);
+    return $call;
   }
 
   /**
@@ -130,9 +123,8 @@ class BaseStub {
   public function _bidiRequest($method,
                                callable $deserialize,
                                $metadata = array()) {
-    return new BidiStreamingSurfaceActiveCall($this->channel,
-                                              $method,
-                                              $deserialize,
-                                              $metadata);
+    $call = new BidiStreamingCall($this->channel, $method, $deserialize);
+    $call->start($metadata);
+    return $call;
   }
 }
diff --git a/src/php/lib/Grpc/BidiStreamingSurfaceActiveCall.php b/src/php/lib/Grpc/BidiStreamingCall.php
old mode 100755
new mode 100644
similarity index 64%
rename from src/php/lib/Grpc/BidiStreamingSurfaceActiveCall.php
rename to src/php/lib/Grpc/BidiStreamingCall.php
index 0459f21e2795cdcec50f69e3b7c14c3dfb08dc51..454f7621ae4b0c92b1541f68a89aa226ec354feb
--- a/src/php/lib/Grpc/BidiStreamingSurfaceActiveCall.php
+++ b/src/php/lib/Grpc/BidiStreamingCall.php
@@ -32,44 +32,57 @@
  *
  */
 namespace Grpc;
-require_once realpath(dirname(__FILE__) . '/../autoload.php');
 
 /**
  * Represents an active call that allows for sending and recieving messages in
  * streams in any order.
  */
-class BidiStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall {
+class BidiStreamingCall extends AbstractCall {
+  /**
+   * Start the call
+   * @param array $metadata Metadata to send with the call, if applicable
+   */
+  public function start($metadata) {
+    $event = $this->call->start_batch([
+        OP_SEND_INITIAL_METADATA => $metadata,
+        OP_RECV_INITIAL_METADATA => true]);
+    $this->metadata = $event->metadata;
+  }
 
   /**
    * Reads the next value from the server.
    * @return The next value from the server, or null if there is none
    */
   public function read() {
-    return $this->_read();
+    $read_event = $this->call->start_batch([OP_RECV_MESSAGE => true]);
+    return $this->deserializeResponse($read_event->message);
   }
 
   /**
-   * Writes a single message to the server. This cannot be called after
+   * Write a single message to the server. This cannot be called after
    * writesDone is called.
-   * @param $value The message to send
+   * @param ByteBuffer $data The data to write
    */
-  public function write($value) {
-    $this->_write($value);
+  public function write($data) {
+    $this->call->start_batch([OP_SEND_MESSAGE => $data->serialize()]);
   }
 
   /**
-   * Indicate that no more writes will be sent
+   * Indicate that no more writes will be sent.
    */
   public function writesDone() {
-    $this->_writesDone();
+    $this->call->start_batch([OP_SEND_CLOSE_FROM_CLIENT => true]);
   }
 
   /**
    * Wait for the server to send the status, and return it.
-   * @return object The status object, with integer $code and string $details
-   *     members
+   * @return object The status object, with integer $code, string $details,
+   *     and array $metadata members
    */
   public function getStatus() {
-    return $this->_getStatus();
+    $status_event = $this->call->start_batch([
+        OP_RECV_STATUS_ON_CLIENT => true
+                                              ]);
+    return $status_event->status;
   }
-}
+}
\ No newline at end of file
diff --git a/src/php/lib/Grpc/ClientStreamingSurfaceActiveCall.php b/src/php/lib/Grpc/ClientStreamingCall.php
old mode 100755
new mode 100644
similarity index 70%
rename from src/php/lib/Grpc/ClientStreamingSurfaceActiveCall.php
rename to src/php/lib/Grpc/ClientStreamingCall.php
index d33f09fbe4e36ca59e0434aaef82e42588807e97..fa29037b4283b3cdf95dbed8aee728412c519b2c
--- a/src/php/lib/Grpc/ClientStreamingSurfaceActiveCall.php
+++ b/src/php/lib/Grpc/ClientStreamingCall.php
@@ -32,31 +32,26 @@
  *
  */
 namespace Grpc;
-require_once realpath(dirname(__FILE__) . '/../autoload.php');
 
 /**
  * Represents an active call that sends a stream of messages and then gets a
  * single response.
  */
-class ClientStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall {
+class ClientStreamingCall extends AbstractCall {
   /**
-   * Create a new simple (single request/single response) active call.
-   * @param Channel $channel The channel to communicate on
-   * @param string $method The method to call on the remote server
-   * @param callable $deserialize The function to deserialize a value
+   * Start the call.
    * @param Traversable $arg_iter The iterator of arguments to send
    * @param array $metadata Metadata to send with the call, if applicable
    */
-  public function __construct(Channel $channel,
-                              $method,
-                              callable $deserialize,
-                              $arg_iter,
-                              $metadata = array()) {
-    parent::__construct($channel, $method, $deserialize, $metadata, 0);
+  public function start($arg_iter, $metadata = array()) {
+    $event = $this->call->start_batch([
+        OP_SEND_INITIAL_METADATA => $metadata,
+        OP_RECV_INITIAL_METADATA => true]);
+    $this->metadata = $event->metadata;
     foreach($arg_iter as $arg) {
-      $this->_write($arg);
+      $this->call->start_batch([OP_SEND_MESSAGE => $arg->serialize()]);
     }
-    $this->_writesDone();
+    $this->call->start_batch([OP_SEND_CLOSE_FROM_CLIENT => true]);
   }
 
   /**
@@ -64,8 +59,9 @@ class ClientStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall {
    * @return [response data, status]
    */
   public function wait() {
-    $response = $this->_read();
-    $status = $this->_getStatus();
-    return array($response, $status);
+    $event = $this->call->start_batch([
+        OP_RECV_MESSAGE => true,
+        OP_RECV_STATUS_ON_CLIENT => true]);
+    return array($this->deserializeResponse($event->message), $event->status);
   }
-}
+}
\ No newline at end of file
diff --git a/src/php/lib/Grpc/ServerStreamingSurfaceActiveCall.php b/src/php/lib/Grpc/ServerStreamingCall.php
old mode 100755
new mode 100644
similarity index 66%
rename from src/php/lib/Grpc/ServerStreamingSurfaceActiveCall.php
rename to src/php/lib/Grpc/ServerStreamingCall.php
index fd08e86e51334430a0a93b5f77fe121efeb21205..574c1bb1e028e981d41873e4bc2736abcdb50821
--- a/src/php/lib/Grpc/ServerStreamingSurfaceActiveCall.php
+++ b/src/php/lib/Grpc/ServerStreamingCall.php
@@ -33,42 +33,45 @@
  */
 namespace Grpc;
 
-require_once realpath(dirname(__FILE__) . '/../autoload.php');
-
 /**
  * Represents an active call that sends a single message and then gets a stream
  * of reponses
  */
-class ServerStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall {
+class ServerStreamingCall extends AbstractCall {
   /**
-   * Create a new simple (single request/single response) active call.
-   * @param Channel $channel The channel to communicate on
-   * @param string $method The method to call on the remote server
-   * @param callable $deserialize The function to deserialize a value
+   * Start the call
    * @param $arg The argument to send
    * @param array $metadata Metadata to send with the call, if applicable
    */
-  public function __construct(Channel $channel,
-                              $method,
-                              callable $deserialize,
-                              $arg,
-                              $metadata = array()) {
-    parent::__construct($channel, $method, $deserialize, $metadata,
-                        \Grpc\WRITE_BUFFER_HINT);
-    $this->_write($arg);
-    $this->_writesDone();
+  public function start($arg, $metadata = array()) {
+    $event = $this->call->start_batch([
+        OP_SEND_INITIAL_METADATA => $metadata,
+        OP_RECV_INITIAL_METADATA => true,
+        OP_SEND_MESSAGE => $arg->serialize(),
+        OP_SEND_CLOSE_FROM_CLIENT => true]);
+    $this->metadata = $event->metadata;
   }
 
   /**
    * @return An iterator of response values
    */
   public function responses() {
-    while(($response = $this->_read()) !== null) {
-      yield $response;
+    $response = $this->call->start_batch([OP_RECV_MESSAGE => true])->message;
+    while($response !== null) {
+      yield $this->deserializeResponse($response);
+      $response = $this->call->start_batch([OP_RECV_MESSAGE => true])->message;
     }
   }
 
+  /**
+   * Wait for the server to send the status, and return it.
+   * @return object The status object, with integer $code, string $details,
+   *     and array $metadata members
+   */
   public function getStatus() {
-    return $this->_getStatus();
+    $status_event = $this->call->start_batch([
+        OP_RECV_STATUS_ON_CLIENT => true
+                                              ]);
+    return $status_event->status;
   }
-}
+}
\ No newline at end of file
diff --git a/src/php/lib/Grpc/SimpleSurfaceActiveCall.php b/src/php/lib/Grpc/UnaryCall.php
old mode 100755
new mode 100644
similarity index 68%
rename from src/php/lib/Grpc/SimpleSurfaceActiveCall.php
rename to src/php/lib/Grpc/UnaryCall.php
index ba82f5704f58f94f0918c6f4b9ecae1b077e28c6..814d477697accf61ea2d6c536e592160dd5fbae1
--- a/src/php/lib/Grpc/SimpleSurfaceActiveCall.php
+++ b/src/php/lib/Grpc/UnaryCall.php
@@ -33,30 +33,23 @@
  */
 namespace Grpc;
 
-require_once realpath(dirname(__FILE__) . '/../autoload.php');
-
 /**
  * Represents an active call that sends a single message and then gets a single
  * response.
  */
-class SimpleSurfaceActiveCall extends AbstractSurfaceActiveCall {
+class UnaryCall extends AbstractCall {
   /**
-   * Create a new simple (single request/single response) active call.
-   * @param Channel $channel The channel to communicate on
-   * @param string $method The method to call on the remote server
-   * @param callable $deserialize The function to deserialize a value
+   * Start the call
    * @param $arg The argument to send
    * @param array $metadata Metadata to send with the call, if applicable
    */
-  public function __construct(Channel $channel,
-                              $method,
-                              callable $deserialize,
-                              $arg,
-                              $metadata = array()) {
-    parent::__construct($channel, $method, $deserialize, $metadata,
-                        \Grpc\WRITE_BUFFER_HINT);
-    $this->_write($arg);
-    $this->_writesDone();
+  public function start($arg, $metadata = array()) {
+    $event = $this->call->start_batch([
+        OP_SEND_INITIAL_METADATA => $metadata,
+        OP_RECV_INITIAL_METADATA => true,
+        OP_SEND_MESSAGE => $arg->serialize(),
+        OP_SEND_CLOSE_FROM_CLIENT => true]);
+    $this->metadata = $event->metadata;
   }
 
   /**
@@ -64,8 +57,9 @@ class SimpleSurfaceActiveCall extends AbstractSurfaceActiveCall {
    * @return [response data, status]
    */
   public function wait() {
-    $response = $this->_read();
-    $status = $this->_getStatus();
-    return array($response, $status);
+    $event = $this->call->start_batch([
+        OP_RECV_MESSAGE => true,
+        OP_RECV_STATUS_ON_CLIENT => true]);
+    return array($this->deserializeResponse($event->message), $event->status);
   }
-}
+}
\ No newline at end of file
diff --git a/src/php/tests/generated_code/GeneratedCodeTest.php b/src/php/tests/generated_code/GeneratedCodeTest.php
index cb2c0e6d10624dd62f6947aec5a395850f224224..afd7f21de42be4c7ca70510e2ba3bfb3473302eb 100755
--- a/src/php/tests/generated_code/GeneratedCodeTest.php
+++ b/src/php/tests/generated_code/GeneratedCodeTest.php
@@ -31,7 +31,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  */
-require_once realpath(dirname(__FILE__) . '/../../lib/autoload.php');
+require_once realpath(dirname(__FILE__) . '/../../vendor/autoload.php');
 require 'DrSlump/Protobuf.php';
 \DrSlump\Protobuf::autoload();
 require 'math.php';
diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php
index 82ca4381690e702aae5873f25d1a5731ed74be96..873fa00b827619d9e54aa5ce502f1b84153963f2 100755
--- a/src/php/tests/interop/interop_client.php
+++ b/src/php/tests/interop/interop_client.php
@@ -31,7 +31,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  */
-require_once realpath(dirname(__FILE__) . '/../../lib/autoload.php');
+require_once realpath(dirname(__FILE__) . '/../../vendor/autoload.php');
 require 'DrSlump/Protobuf.php';
 \DrSlump\Protobuf::autoload();
 require 'empty.php';
@@ -132,8 +132,6 @@ function serverStreaming($stub) {
   }
 
   $call = $stub->StreamingOutputCall($request);
-  hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
-              'Call did not complete successfully');
   $i = 0;
   foreach($call->responses() as $value) {
     hardAssert($i < 4, 'Too many responses');
@@ -142,7 +140,10 @@ function serverStreaming($stub) {
                 'Payload ' . $i . ' had the wrong type');
     hardAssert(strlen($payload->getBody()) === $sizes[$i],
                 'Response ' . $i . ' had the wrong length');
+    $i += 1;
   }
+  hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
+             'Call did not complete successfully');
 }
 
 /**
@@ -240,4 +241,6 @@ switch($args['test_case']) {
     break;
   case 'cancel_after_first_response':
     cancelAfterFirstResponse($stub);
+  default:
+    exit(1);
 }
diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php
index 8bb0927f2125cab66aeafa80c3b629bf998b42fb..d361ce0030d6eea85237ae0a059a6da884731662 100755
--- a/src/php/tests/unit_tests/CallTest.php
+++ b/src/php/tests/unit_tests/CallTest.php
@@ -36,65 +36,47 @@ class CallTest extends PHPUnit_Framework_TestCase{
   static $port;
 
   public static function setUpBeforeClass() {
-    $cq = new Grpc\CompletionQueue();
-    self::$server = new Grpc\Server($cq, []);
+    self::$server = new Grpc\Server([]);
     self::$port = self::$server->add_http2_port('0.0.0.0:0');
   }
 
   public function setUp() {
-    $this->cq = new Grpc\CompletionQueue();
     $this->channel = new Grpc\Channel('localhost:' . self::$port, []);
     $this->call = new Grpc\Call($this->channel,
                                 '/foo',
                                 Grpc\Timeval::inf_future());
   }
 
-  /**
-   * @expectedException LogicException
-   * @expectedExceptionCode Grpc\CALL_ERROR_INVALID_FLAGS
-   * @expectedExceptionMessage invoke
-   */
-  public function testInvokeRejectsBadFlags() {
-    $this->call->invoke($this->cq, 0, 0, 0xDEADBEEF);
-  }
-
-  /**
-   * @expectedException LogicException
-   * @expectedExceptionCode Grpc\CALL_ERROR_NOT_ON_CLIENT
-   * @expectedExceptionMessage server_accept
-   */
-  public function testServerAcceptFailsCorrectly() {
-    $this->call->server_accept($this->cq, 0);
-  }
-
-  /* These test methods with assertTrue(true) at the end just check that the
-     method calls completed without errors. PHPUnit warns for tests with no
-     asserts, and this avoids that warning without changing the meaning of the
-     tests */
-
   public function testAddEmptyMetadata() {
-    $this->call->add_metadata([], 0);
-    /* Dummy assert: Checks that the previous call completed without error */
-    $this->assertTrue(true);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => []
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 
   public function testAddSingleMetadata() {
-    $this->call->add_metadata(['key' => ['value']], 0);
-    /* Dummy assert: Checks that the previous call completed without error */
-    $this->assertTrue(true);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => ['key' => ['value']]
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 
   public function testAddMultiValueMetadata() {
-    $this->call->add_metadata(['key' => ['value1', 'value2']], 0);
-    /* Dummy assert: Checks that the previous call completed without error */
-    $this->assertTrue(true);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => ['key' => ['value1', 'value2']]
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 
   public function testAddSingleAndMultiValueMetadata() {
-    $this->call->add_metadata(
-        ['key1' => ['value1'],
-         'key2' => ['value2', 'value3']], 0);
-    /* Dummy assert: Checks that the previous call completed without error */
-    $this->assertTrue(true);
+    $batch = [
+        Grpc\OP_SEND_INITIAL_METADATA => ['key1' => ['value1'],
+                                          'key2' => ['value2', 'value3']]
+              ];
+    $result = $this->call->start_batch($batch);
+    $this->assertTrue($result->send_metadata);
   }
 }
diff --git a/src/php/tests/unit_tests/CompletionQueueTest.php b/src/php/tests/unit_tests/CompletionQueueTest.php
deleted file mode 100755
index 76ee61dfe8eea7b4db22ad9413670876c52423b9..0000000000000000000000000000000000000000
--- a/src/php/tests/unit_tests/CompletionQueueTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-/*
- *
- * 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.
- *
- */
-class CompletionQueueTest extends PHPUnit_Framework_TestCase{
-  public function testNextReturnsNullWithNoCall() {
-    $cq = new Grpc\CompletionQueue();
-    $event = $cq->next(Grpc\Timeval::zero());
-    $this->assertNull($event);
-  }
-
-  public function testPluckReturnsNullWithNoCall() {
-    $cq = new Grpc\CompletionQueue();
-    $event = $cq->pluck(0, Grpc\Timeval::zero());
-    $this->assertNull($event);
-  }
-}
diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php
index 0cbc506c8e4c2170975fb657bbce1e4fdbfce767..3e165b7213dbc98e1b63441a5c2fa225ad5363c3 100755
--- a/src/php/tests/unit_tests/EndToEndTest.php
+++ b/src/php/tests/unit_tests/EndToEndTest.php
@@ -33,18 +33,15 @@
  */
 class EndToEndTest extends PHPUnit_Framework_TestCase{
   public function setUp() {
-    $this->client_queue = new Grpc\CompletionQueue();
-    $this->server_queue = new Grpc\CompletionQueue();
-    $this->server = new Grpc\Server($this->server_queue, []);
+    $this->server = new Grpc\Server([]);
     $port = $this->server->add_http2_port('0.0.0.0:0');
     $this->channel = new Grpc\Channel('localhost:' . $port, []);
+    $this->server->start();
   }
 
   public function tearDown() {
     unset($this->channel);
     unset($this->server);
-    unset($this->client_queue);
-    unset($this->server_queue);
   }
 
   public function testSimpleRequestBody() {
@@ -53,55 +50,45 @@ class EndToEndTest extends PHPUnit_Framework_TestCase{
     $call = new Grpc\Call($this->channel,
                           'dummy_method',
                           $deadline);
-    $tag = 1;
-    $call->invoke($this->client_queue, $tag, $tag);
-    $server_tag = 2;
-
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // check that a server rpc new was received
-    $this->server->start();
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
-    $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
-
-    $server_call->server_end_initial_metadata();
 
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true
+                                       ]);
 
-    // the server sends the status
-    $server_call->start_write_status(Grpc\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
 
-    // the client gets CLIENT_METADATA_READ
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
+    $this->assertSame([], $event->metadata);
+    $server_call = $event->call;
 
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true
+                                        ]);
+
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertFalse($event->cancelled);
+
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true
+                                 ]);
+
+    $this->assertSame([], $event->metadata);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
-
     unset($call);
     unset($server_call);
   }
@@ -115,79 +102,52 @@ class EndToEndTest extends PHPUnit_Framework_TestCase{
     $call = new Grpc\Call($this->channel,
                           'dummy_method',
                           $deadline);
-    $tag = 1;
-    $call->invoke($this->client_queue, $tag, $tag);
 
-    $server_tag = 2;
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
+        Grpc\OP_SEND_MESSAGE => $req_text
+                                       ]);
 
-    // the client writes
-    $call->start_write($req_text, $tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
+    $this->assertTrue($event->send_message);
 
-    // check that a server rpc new was received
-    $this->server->start();
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
     $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
-
-    $server_call->server_end_initial_metadata();
-
-    // start the server read
-    $server_call->start_read($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($req_text, $event->data);
-
-    // the server replies
-    $server_call->start_write($reply_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
-
-    // the client reads the metadata
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
-
-    // the client reads the reply
-    $call->start_read($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($reply_text, $event->data);
-
-    // the client sends writes done
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the server sends the status
-    $server_call->start_write_status(GRPC\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_MESSAGE => $reply_text,
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true,
+                                        ]);
+
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertTrue($event->send_message);
+    $this->assertFalse($event->cancelled);
+    $this->assertSame($req_text, $event->message);
+
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true,
+                                       ]);
+
+    $this->assertSame([], $event->metadata);
+    $this->assertSame($reply_text, $event->message);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-
     unset($call);
     unset($server_call);
   }
diff --git a/src/php/tests/unit_tests/SecureEndToEndTest.php b/src/php/tests/unit_tests/SecureEndToEndTest.php
index 896afeac49ad7fa656eb3f8e7f9dfa221bf0ce25..2d62fe9d5e6bf56b80cf6a5be45d2ef32e3fa225 100755
--- a/src/php/tests/unit_tests/SecureEndToEndTest.php
+++ b/src/php/tests/unit_tests/SecureEndToEndTest.php
@@ -33,17 +33,16 @@
  */
 class SecureEndToEndTest extends PHPUnit_Framework_TestCase{
   public function setUp() {
-    $this->client_queue = new Grpc\CompletionQueue();
-    $this->server_queue = new Grpc\CompletionQueue();
     $credentials = Grpc\Credentials::createSsl(
         file_get_contents(dirname(__FILE__) . '/../data/ca.pem'));
     $server_credentials = Grpc\ServerCredentials::createSsl(
         null,
         file_get_contents(dirname(__FILE__) . '/../data/server1.key'),
         file_get_contents(dirname(__FILE__) . '/../data/server1.pem'));
-    $this->server = new Grpc\Server($this->server_queue);
+    $this->server = new Grpc\Server();
     $port = $this->server->add_secure_http2_port('0.0.0.0:0',
                                                  $server_credentials);
+    $this->server->start();
     $this->channel = new Grpc\Channel(
         'localhost:' . $port,
         [
@@ -55,70 +54,58 @@ class SecureEndToEndTest extends PHPUnit_Framework_TestCase{
   public function tearDown() {
     unset($this->channel);
     unset($this->server);
-    unset($this->client_queue);
-    unset($this->server_queue);
   }
 
   public function testSimpleRequestBody() {
-    $this->server->start();
     $deadline = Grpc\Timeval::inf_future();
     $status_text = 'xyz';
     $call = new Grpc\Call($this->channel,
                           'dummy_method',
                           $deadline);
-    $tag = 1;
-    $call->invoke($this->client_queue, $tag, $tag);
-    $server_tag = 2;
-
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // check that a server rpc new was received
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
+
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true
+                                       ]);
+
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
+
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
+    $this->assertSame([], $event->metadata);
     $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
-
-    $server_call->server_end_initial_metadata();
-
-    // the server sends the status
-    $server_call->start_write_status(Grpc\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the client gets CLIENT_METADATA_READ
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
-
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true
+                                        ]);
+
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertFalse($event->cancelled);
+
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true
+                                 ]);
+
+    $this->assertSame([], $event->metadata);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
-
     unset($call);
     unset($server_call);
   }
 
   public function testClientServerFullRequestResponse() {
-    $this->server->start();
     $deadline = Grpc\Timeval::inf_future();
     $req_text = 'client_server_full_request_response';
     $reply_text = 'reply:client_server_full_request_response';
@@ -127,78 +114,52 @@ class SecureEndToEndTest extends PHPUnit_Framework_TestCase{
     $call = new Grpc\Call($this->channel,
                           'dummy_method',
                           $deadline);
-    $tag = 1;
-    $call->invoke($this->client_queue, $tag, $tag);
-
-    $server_tag = 2;
-
-    // the client writes
-    $call->start_write($req_text, $tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
-
-    // check that a server rpc new was received
-    $this->server->request_call($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\SERVER_RPC_NEW, $event->type);
+
+    $event = $call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
+        Grpc\OP_SEND_MESSAGE => $req_text
+                                       ]);
+
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_close);
+    $this->assertTrue($event->send_message);
+
+    $event = $this->server->request_call();
+    $this->assertSame('dummy_method', $event->method);
     $server_call = $event->call;
-    $this->assertNotNull($server_call);
-    $server_call->server_accept($this->server_queue, $server_tag);
-
-    $server_call->server_end_initial_metadata();
-
-    // start the server read
-    $server_call->start_read($server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($req_text, $event->data);
-
-    // the server replies
-    $server_call->start_write($reply_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\WRITE_ACCEPTED, $event->type);
-
-    // the client reads the metadata
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\CLIENT_METADATA_READ, $event->type);
-
-    // the client reads the reply
-    $call->start_read($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\READ, $event->type);
-    $this->assertSame($reply_text, $event->data);
-
-    // the client sends writes done
-    $call->writes_done($tag);
-    $event = $this->client_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the server sends the status
-    $server_call->start_write_status(GRPC\STATUS_OK, $status_text, $server_tag);
-    $event = $this->server_queue->next($deadline);
-    $this->assertSame(Grpc\FINISH_ACCEPTED, $event->type);
-    $this->assertSame(Grpc\OP_OK, $event->data);
-
-    // the client gets FINISHED
-    $event = $this->client_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-    $status = $event->data;
+
+    $event = $server_call->start_batch([
+        Grpc\OP_SEND_INITIAL_METADATA => [],
+        Grpc\OP_SEND_MESSAGE => $reply_text,
+        Grpc\OP_SEND_STATUS_FROM_SERVER => [
+            'metadata' => [],
+            'code' => Grpc\STATUS_OK,
+            'details' => $status_text
+                                            ],
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_CLOSE_ON_SERVER => true,
+                                        ]);
+
+    $this->assertTrue($event->send_metadata);
+    $this->assertTrue($event->send_status);
+    $this->assertTrue($event->send_message);
+    $this->assertFalse($event->cancelled);
+    $this->assertSame($req_text, $event->message);
+
+    $event = $call->start_batch([
+        Grpc\OP_RECV_INITIAL_METADATA => true,
+        Grpc\OP_RECV_MESSAGE => true,
+        Grpc\OP_RECV_STATUS_ON_CLIENT => true,
+                                       ]);
+
+    $this->assertSame([], $event->metadata);
+    $this->assertSame($reply_text, $event->message);
+    $status = $event->status;
+    $this->assertSame([], $status->metadata);
     $this->assertSame(Grpc\STATUS_OK, $status->code);
     $this->assertSame($status_text, $status->details);
 
-    // and the server gets FINISHED
-    $event = $this->server_queue->next($deadline);
-    $this->assertNotNull($event);
-    $this->assertSame(Grpc\FINISHED, $event->type);
-
     unset($call);
     unset($server_call);
   }
diff --git a/src/ruby/Rakefile b/src/ruby/Rakefile
index b27305d16c3abfa766e26ab2bcb8f942bc76ef0c..afb354e922826738a9401bd606d9cccad2b6c960 100755
--- a/src/ruby/Rakefile
+++ b/src/ruby/Rakefile
@@ -2,14 +2,17 @@
 require 'rake/extensiontask'
 require 'rspec/core/rake_task'
 require 'rubocop/rake_task'
+require 'bundler/gem_tasks'
 
-desc 'Run Rubocop to check for style violations'
+# Add rubocop style checking tasks
 RuboCop::RakeTask.new
 
+# Add the extension compiler task
 Rake::ExtensionTask.new 'grpc' do |ext|
   ext.lib_dir = File.join('lib', 'grpc')
 end
 
+# Define the test suites
 SPEC_SUITES = [
   { id: :wrapper, title: 'wrapper layer', files: %w(spec/*.rb) },
   { id: :idiomatic, title: 'idiomatic layer', dir: %w(spec/generic),
@@ -19,36 +22,34 @@ SPEC_SUITES = [
   { id: :server, title: 'rpc server thread tests', dir: %w(spec/generic),
     tag: 'server' }
 ]
+namespace :suite do
+  SPEC_SUITES.each do |suite|
+    desc "Run all specs in the #{suite[:title]} spec suite"
+    RSpec::Core::RakeTask.new(suite[:id]) do |t|
+      spec_files = []
+      suite[:files].each { |f| spec_files += Dir[f] } if suite[:files]
+
+      if suite[:dir]
+        suite[:dir].each { |f| spec_files += Dir["#{f}/**/*_spec.rb"] }
+      end
+      helper = 'spec/spec_helper.rb'
+      spec_files << helper unless spec_files.include?(helper)
 
-desc 'Run all RSpec tests'
-namespace :spec do
-  namespace :suite do
-    SPEC_SUITES.each do |suite|
-      desc "Run all specs in #{suite[:title]} spec suite"
-      RSpec::Core::RakeTask.new(suite[:id]) do |t|
-        spec_files = []
-        suite[:files].each { |f| spec_files += Dir[f] } if suite[:files]
-
-        if suite[:dirs]
-          suite[:dirs].each { |f| spec_files += Dir["#{f}/**/*_spec.rb"] }
-        end
-
-        t.pattern = spec_files
-        t.rspec_opts = "--tag #{suite[:tag]}" if suite[:tag]
-        if suite[:tags]
-          t.rspec_opts = suite[:tags].map { |x| "--tag #{x}" }.join(' ')
-        end
+      t.pattern = spec_files
+      t.rspec_opts = "--tag #{suite[:tag]}" if suite[:tag]
+      if suite[:tags]
+        t.rspec_opts = suite[:tags].map { |x| "--tag #{x}" }.join(' ')
       end
     end
   end
 end
 
-desc 'Compiles the extension then runs all the tests'
-task :all
+# Define dependencies between the suites.
+task 'suite:wrapper' => [:compile, :rubocop]
+task 'suite:idiomatic' => 'suite:wrapper'
+task 'suite:bidi' => 'suite:wrapper'
+task 'suite:server' => 'suite:wrapper'
 
+desc 'Compiles the gRPC extension then runs all the tests'
+task all: ['suite:idiomatic', 'suite:bidi', 'suite:server']
 task default: :all
-task 'spec:suite:wrapper' => [:compile, :rubocop]
-task 'spec:suite:idiomatic' => 'spec:suite:wrapper'
-task 'spec:suite:bidi' => 'spec:suite:wrapper'
-task 'spec:suite:server' => 'spec:suite:wrapper'
-task all: ['spec:suite:idiomatic', 'spec:suite:bidi', 'spec:suite:server']
diff --git a/src/ruby/lib/grpc/generic/service.rb b/src/ruby/lib/grpc/generic/service.rb
index 6ea0831a2e64265ff9dd81920327f2dc42c198a4..69076b4c6e8ea0ea836cf9ddd4c722d63db085e2 100644
--- a/src/ruby/lib/grpc/generic/service.rb
+++ b/src/ruby/lib/grpc/generic/service.rb
@@ -176,25 +176,26 @@ module GRPC
             unmarshal = desc.unmarshal_proc(:output)
             route = "/#{route_prefix}/#{name}"
             if desc.request_response?
-              define_method(mth_name) do |req, deadline = nil|
+              define_method(mth_name) do |req, deadline = nil, **kw|
                 logger.debug("calling #{@host}:#{route}")
-                request_response(route, req, marshal, unmarshal, deadline)
+                request_response(route, req, marshal, unmarshal, deadline, **kw)
               end
             elsif desc.client_streamer?
-              define_method(mth_name) do |reqs, deadline = nil|
+              define_method(mth_name) do |reqs, deadline = nil, **kw|
                 logger.debug("calling #{@host}:#{route}")
-                client_streamer(route, reqs, marshal, unmarshal, deadline)
+                client_streamer(route, reqs, marshal, unmarshal, deadline, **kw)
               end
             elsif desc.server_streamer?
-              define_method(mth_name) do |req, deadline = nil, &blk|
+              define_method(mth_name) do |req, deadline = nil, **kw, &blk|
                 logger.debug("calling #{@host}:#{route}")
-                server_streamer(route, req, marshal, unmarshal, deadline,
+                server_streamer(route, req, marshal, unmarshal, deadline, **kw,
                                 &blk)
               end
             else  # is a bidi_stream
-              define_method(mth_name) do |reqs, deadline = nil, &blk|
+              define_method(mth_name) do |reqs, deadline = nil, **kw, &blk|
                 logger.debug("calling #{@host}:#{route}")
-                bidi_streamer(route, reqs, marshal, unmarshal, deadline, &blk)
+                bidi_streamer(route, reqs, marshal, unmarshal, deadline, **kw,
+                              &blk)
               end
             end
           end
diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb
index 8914225b558c5b91125af648bb9d56254218af16..96e07cacb44ae3351a6a502225742e23b8653784 100644
--- a/src/ruby/spec/generic/active_call_spec.rb
+++ b/src/ruby/spec/generic/active_call_spec.rb
@@ -67,7 +67,7 @@ describe GRPC::ActiveCall do
     end
 
     describe '#multi_req_view' do
-      xit 'exposes a fixed subset of the ActiveCall methods' do
+      it 'exposes a fixed subset of the ActiveCall methods' do
         want = %w(cancelled, deadline, each_remote_read, metadata, shutdown)
         v = @client_call.multi_req_view
         want.each do |w|
@@ -77,7 +77,7 @@ describe GRPC::ActiveCall do
     end
 
     describe '#single_req_view' do
-      xit 'exposes a fixed subset of the ActiveCall methods' do
+      it 'exposes a fixed subset of the ActiveCall methods' do
         want = %w(cancelled, deadline, metadata, shutdown)
         v = @client_call.single_req_view
         want.each do |w|
diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb
index 73f2d37e305bdef89aa4ea71fa0b21822369e5d9..0c98fc40d91a5949d2142c232d75e0151b4273c7 100644
--- a/src/ruby/spec/generic/client_stub_spec.rb
+++ b/src/ruby/spec/generic/client_stub_spec.rb
@@ -384,13 +384,7 @@ describe 'ClientStub' do
         th.join
       end
 
-      # disabled because an unresolved wire-protocol implementation feature
-      #
-      # - servers should be able initiate messaging, however, as it stand
-      # servers don't know if all the client metadata has been sent until
-      # they receive a message from the client.  Without receiving all the
-      # metadata, the server does not accept the call, so this test hangs.
-      xit 'supports a server-initiated ping pong', bidi: true do
+      it 'supports a server-initiated ping pong', bidi: true do
         server_port = create_test_server
         host = "localhost:#{server_port}"
         th = run_bidi_streamer_echo_ping_pong(@sent_msgs, @pass, false)
diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb
index f3b89b5895eb9ee8771dc7fdbaaf71dd6a422783..34e5cdcd04cd9389e02ed845c9a3dc5d27dae8b9 100644
--- a/src/ruby/spec/generic/rpc_server_spec.rb
+++ b/src/ruby/spec/generic/rpc_server_spec.rb
@@ -81,14 +81,17 @@ EchoStub = EchoService.rpc_stub_class
 class SlowService
   include GRPC::GenericService
   rpc :an_rpc, EchoMsg, EchoMsg
+  attr_reader :received_md, :delay
 
   def initialize(_default_var = 'ignored')
+    @delay = 0.25
+    @received_md = []
   end
 
-  def an_rpc(req, _call)
-    delay = 0.25
-    logger.info("starting a slow #{delay} rpc")
-    sleep delay
+  def an_rpc(req, call)
+    logger.info("starting a slow #{@delay} rpc")
+    sleep @delay
+    @received_md << call.metadata unless call.metadata.nil?
     req  # send back the req as the response
   end
 end
@@ -354,6 +357,37 @@ describe GRPC::RpcServer do
         t.join
       end
 
+      it 'should receive metadata when a deadline is specified', server: true do
+        service = SlowService.new
+        @srv.handle(service)
+        t = Thread.new { @srv.run }
+        @srv.wait_till_running
+        req = EchoMsg.new
+        stub = SlowStub.new(@host, **@client_opts)
+        deadline = service.delay + 0.5 # wait for long enough
+        expect(stub.an_rpc(req, deadline, k1: 'v1', k2: 'v2')).to be_a(EchoMsg)
+        wanted_md = [{ 'k1' => 'v1', 'k2' => 'v2' }]
+        expect(service.received_md).to eq(wanted_md)
+        @srv.stop
+        t.join
+      end
+
+      it 'should not receive metadata if the client times out', server: true do
+        service = SlowService.new
+        @srv.handle(service)
+        t = Thread.new { @srv.run }
+        @srv.wait_till_running
+        req = EchoMsg.new
+        stub = SlowStub.new(@host, **@client_opts)
+        deadline = 0.1  # too short for SlowService to respond
+        blk = proc { stub.an_rpc(req, deadline, k1: 'v1', k2: 'v2') }
+        expect(&blk).to raise_error GRPC::BadStatus
+        wanted_md = []
+        expect(service.received_md).to eq(wanted_md)
+        @srv.stop
+        t.join
+      end
+
       it 'should receive updated metadata', server: true do
         service = EchoService.new
         @srv.handle(service)
diff --git a/test/core/tsi/transport_security_test.c b/test/core/tsi/transport_security_test.c
index c5882af966cf169363984d2cde7d4574b3b94588..d591e43faae80e78f76ed0826dbedf1027cb2a2f 100644
--- a/test/core/tsi/transport_security_test.c
+++ b/test/core/tsi/transport_security_test.c
@@ -39,10 +39,15 @@
 #include <grpc/support/log.h>
 #include <grpc/support/useful.h>
 
+#include <openssl/crypto.h>
+
 #include "src/core/support/string.h"
 #include "src/core/tsi/ssl_transport_security.h"
 #include "test/core/util/test_config.h"
 
+/* Currently points to 1.0.2a. */
+#define GRPC_MIN_OPENSSL_VERSION_NUMBER 0x1000201fL
+
 typedef struct {
   /* 1 if success, 0 if failure. */
   int expected;
@@ -296,8 +301,13 @@ static void test_peer_matches_name(void) {
   }
 }
 
+static void test_openssl_version(void) {
+  GPR_ASSERT(OPENSSL_VERSION_NUMBER >= GRPC_MIN_OPENSSL_VERSION_NUMBER);
+}
+
 int main(int argc, char **argv) {
   grpc_test_init(argc, argv);
   test_peer_matches_name();
+  test_openssl_version();
   return 0;
 }
diff --git a/test/cpp/end2end/async_end2end_test.cc b/test/cpp/end2end/async_end2end_test.cc
index 4c71831dec8eeba1bd0bf1bd11f1b8e9ff572a65..9938fcf12eb700fbb7e6f902e6426cdf115437e5 100644
--- a/test/cpp/end2end/async_end2end_test.cc
+++ b/test/cpp/end2end/async_end2end_test.cc
@@ -532,15 +532,19 @@ TEST_F(AsyncEnd2endTest, MetadataRpc) {
   send_request.set_message("Hello");
   std::pair<grpc::string, grpc::string> meta1("key1", "val1");
   std::pair<grpc::string, grpc::string> meta2(
-      "key2-bin", {"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc", 13});
+      "key2-bin",
+      grpc::string("\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc",
+		   13));
   std::pair<grpc::string, grpc::string> meta3("key3", "val3");
   std::pair<grpc::string, grpc::string> meta6(
       "key4-bin",
-      {"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d", 14});
+      grpc::string("\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d",
+		   14));
   std::pair<grpc::string, grpc::string> meta5("key5", "val5");
   std::pair<grpc::string, grpc::string> meta4(
       "key6-bin",
-      {"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee", 15});
+      grpc::string("\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee",
+		   15));
 
   cli_ctx.AddMetadata(meta1.first, meta1.second);
   cli_ctx.AddMetadata(meta2.first, meta2.second);
@@ -595,6 +599,5 @@ int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
   int result = RUN_ALL_TESTS();
   grpc_shutdown();
-  google::protobuf::ShutdownProtobufLibrary();
   return result;
 }
diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc
index 41c266953366d55d89ce74fb386580246ad73c27..e2649c2826ef2d9135b261a67cc2bc0a5788c080 100644
--- a/test/cpp/end2end/end2end_test.cc
+++ b/test/cpp/end2end/end2end_test.cc
@@ -432,6 +432,5 @@ int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
   int result = RUN_ALL_TESTS();
   grpc_shutdown();
-  google::protobuf::ShutdownProtobufLibrary();
   return result;
 }
diff --git a/test/cpp/end2end/generic_end2end_test.cc b/test/cpp/end2end/generic_end2end_test.cc
index 711f1b95405d250c50080719d697df9970ca6b71..9cdd8c94d1299d0a626e3a8ec7cd5a30239ce82d 100644
--- a/test/cpp/end2end/generic_end2end_test.cc
+++ b/test/cpp/end2end/generic_end2end_test.cc
@@ -47,6 +47,7 @@
 #include <grpc++/client_context.h>
 #include <grpc++/create_channel.h>
 #include <grpc++/credentials.h>
+#include <grpc++/generic_stub.h>
 #include <grpc++/server.h>
 #include <grpc++/server_builder.h>
 #include <grpc++/server_context.h>
@@ -83,12 +84,21 @@ bool ParseFromByteBuffer(ByteBuffer* buffer, grpc::protobuf::Message* message) {
   buffer->Dump(&slices);
   grpc::string buf;
   buf.reserve(buffer->Length());
-  for (const Slice& s : slices) {
-    buf.append(reinterpret_cast<const char*>(s.begin()), s.size());
+  for (auto s = slices.begin(); s != slices.end(); s++) {
+    buf.append(reinterpret_cast<const char*>(s->begin()), s->size());
   }
   return message->ParseFromString(buf);
 }
 
+std::unique_ptr<ByteBuffer> SerializeToByteBuffer(
+    grpc::protobuf::Message* message) {
+  grpc::string buf;
+  message->SerializeToString(&buf);
+  gpr_slice s = gpr_slice_from_copied_string(buf.c_str());
+  Slice slice(s, Slice::STEAL_REF);
+  return std::unique_ptr<ByteBuffer>(new ByteBuffer(&slice, 1));
+}
+
 class GenericEnd2endTest : public ::testing::Test {
  protected:
   GenericEnd2endTest() : generic_service_("*") {}
@@ -118,7 +128,7 @@ class GenericEnd2endTest : public ::testing::Test {
   void ResetStub() {
     std::shared_ptr<ChannelInterface> channel = CreateChannel(
         server_address_.str(), InsecureCredentials(), ChannelArguments());
-    stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel));
+    generic_stub_.reset(new GenericStub(channel));
   }
 
   void server_ok(int i) { verify_ok(&srv_cq_, i, true); }
@@ -127,6 +137,7 @@ class GenericEnd2endTest : public ::testing::Test {
   void client_fail(int i) { verify_ok(&cli_cq_, i, false); }
 
   void SendRpc(int num_rpcs) {
+    const grpc::string kMethodName("/grpc.cpp.test.util.TestService/Echo");
     for (int i = 0; i < num_rpcs; i++) {
       EchoRequest send_request;
       EchoRequest recv_request;
@@ -139,35 +150,42 @@ class GenericEnd2endTest : public ::testing::Test {
       GenericServerAsyncReaderWriter stream(&srv_ctx);
 
       send_request.set_message("Hello");
-      std::unique_ptr<ClientAsyncResponseReader<EchoResponse> > response_reader(
-          stub_->AsyncEcho(&cli_ctx, send_request, &cli_cq_, tag(1)));
+      std::unique_ptr<GenericClientAsyncReaderWriter> call =
+          generic_stub_->Call(&cli_ctx, kMethodName, &cli_cq_, tag(1));
       client_ok(1);
+      std::unique_ptr<ByteBuffer> send_buffer =
+          SerializeToByteBuffer(&send_request);
+      call->Write(*send_buffer, tag(2));
+      client_ok(2);
+      call->WritesDone(tag(3));
+      client_ok(3);
 
-      generic_service_.RequestCall(&srv_ctx, &stream, &srv_cq_, tag(2));
+      generic_service_.RequestCall(&srv_ctx, &stream, &srv_cq_, tag(4));
 
-      verify_ok(generic_service_.completion_queue(), 2, true);
+      verify_ok(generic_service_.completion_queue(), 4, true);
       EXPECT_EQ(server_address_.str(), srv_ctx.host());
-      EXPECT_EQ("/grpc.cpp.test.util.TestService/Echo", srv_ctx.method());
+      EXPECT_EQ(kMethodName, srv_ctx.method());
       ByteBuffer recv_buffer;
-      stream.Read(&recv_buffer, tag(3));
-      server_ok(3);
+      stream.Read(&recv_buffer, tag(5));
+      server_ok(5);
       EXPECT_TRUE(ParseFromByteBuffer(&recv_buffer, &recv_request));
       EXPECT_EQ(send_request.message(), recv_request.message());
 
       send_response.set_message(recv_request.message());
-      grpc::string buf;
-      send_response.SerializeToString(&buf);
-      gpr_slice s = gpr_slice_from_copied_string(buf.c_str());
-      Slice slice(s, Slice::STEAL_REF);
-      ByteBuffer send_buffer(&slice, 1);
-      stream.Write(send_buffer, tag(4));
-      server_ok(4);
-
-      stream.Finish(Status::OK, tag(5));
-      server_ok(5);
+      send_buffer = SerializeToByteBuffer(&send_response);
+      stream.Write(*send_buffer, tag(6));
+      server_ok(6);
+
+      stream.Finish(Status::OK, tag(7));
+      server_ok(7);
+
+      recv_buffer.Clear();
+      call->Read(&recv_buffer, tag(8));
+      client_ok(8);
+      EXPECT_TRUE(ParseFromByteBuffer(&recv_buffer, &recv_response));
 
-      response_reader->Finish(&recv_response, &recv_status, tag(4));
-      client_ok(4);
+      call->Finish(&recv_status, tag(9));
+      client_ok(9);
 
       EXPECT_EQ(send_response.message(), recv_response.message());
       EXPECT_TRUE(recv_status.IsOk());
@@ -177,6 +195,7 @@ class GenericEnd2endTest : public ::testing::Test {
   CompletionQueue cli_cq_;
   CompletionQueue srv_cq_;
   std::unique_ptr<grpc::cpp::test::util::TestService::Stub> stub_;
+  std::unique_ptr<grpc::GenericStub> generic_stub_;
   std::unique_ptr<Server> server_;
   AsyncGenericService generic_service_;
   std::ostringstream server_address_;
@@ -196,6 +215,7 @@ TEST_F(GenericEnd2endTest, SequentialRpcs) {
 TEST_F(GenericEnd2endTest, SimpleBidiStreaming) {
   ResetStub();
 
+  const grpc::string kMethodName("/grpc.cpp.test.util.TestService/BidiStream");
   EchoRequest send_request;
   EchoRequest recv_request;
   EchoResponse send_response;
@@ -206,17 +226,19 @@ TEST_F(GenericEnd2endTest, SimpleBidiStreaming) {
   GenericServerAsyncReaderWriter srv_stream(&srv_ctx);
 
   send_request.set_message("Hello");
-  std::unique_ptr<ClientAsyncReaderWriter<EchoRequest, EchoResponse> >
-      cli_stream(stub_->AsyncBidiStream(&cli_ctx, &cli_cq_, tag(1)));
+  std::unique_ptr<GenericClientAsyncReaderWriter> cli_stream =
+      generic_stub_->Call(&cli_ctx, kMethodName, &cli_cq_, tag(1));
   client_ok(1);
 
   generic_service_.RequestCall(&srv_ctx, &srv_stream, &srv_cq_, tag(2));
 
   verify_ok(generic_service_.completion_queue(), 2, true);
   EXPECT_EQ(server_address_.str(), srv_ctx.host());
-  EXPECT_EQ("/grpc.cpp.test.util.TestService/BidiStream", srv_ctx.method());
+  EXPECT_EQ(kMethodName, srv_ctx.method());
 
-  cli_stream->Write(send_request, tag(3));
+  std::unique_ptr<ByteBuffer> send_buffer =
+      SerializeToByteBuffer(&send_request);
+  cli_stream->Write(*send_buffer, tag(3));
   client_ok(3);
 
   ByteBuffer recv_buffer;
@@ -226,22 +248,18 @@ TEST_F(GenericEnd2endTest, SimpleBidiStreaming) {
   EXPECT_EQ(send_request.message(), recv_request.message());
 
   send_response.set_message(recv_request.message());
-  grpc::string buf;
-  send_response.SerializeToString(&buf);
-  gpr_slice s = gpr_slice_from_copied_string(buf.c_str());
-  Slice slice(s, Slice::STEAL_REF);
-  ByteBuffer send_buffer(&slice, 1);
-  srv_stream.Write(send_buffer, tag(5));
+  send_buffer = SerializeToByteBuffer(&send_response);
+  srv_stream.Write(*send_buffer, tag(5));
   server_ok(5);
 
-  cli_stream->Read(&recv_response, tag(6));
+  cli_stream->Read(&recv_buffer, tag(6));
   client_ok(6);
+  EXPECT_TRUE(ParseFromByteBuffer(&recv_buffer, &recv_response));
   EXPECT_EQ(send_response.message(), recv_response.message());
 
   cli_stream->WritesDone(tag(7));
   client_ok(7);
 
-  recv_buffer.Clear();
   srv_stream.Read(&recv_buffer, tag(8));
   server_fail(8);
 
@@ -265,6 +283,5 @@ int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
   int result = RUN_ALL_TESTS();
   grpc_shutdown();
-  google::protobuf::ShutdownProtobufLibrary();
   return result;
 }
diff --git a/test/cpp/interop/server.cc b/test/cpp/interop/server.cc
index eceb600d4ce5a123833622422aa1acc95a96c1ad..780a7370acbf8b8583fda7ee655587c92ef250d8 100644
--- a/test/cpp/interop/server.cc
+++ b/test/cpp/interop/server.cc
@@ -213,8 +213,11 @@ void RunServer() {
   builder.RegisterService(&service);
   std::shared_ptr<ServerCredentials> creds = grpc::InsecureServerCredentials();
   if (FLAGS_enable_ssl) {
-    SslServerCredentialsOptions ssl_opts = {
-        "", {{test_server1_key, test_server1_cert}}};
+    SslServerCredentialsOptions::PemKeyCertPair pkcp = {test_server1_key,
+							test_server1_cert};
+    SslServerCredentialsOptions ssl_opts;
+    ssl_opts.pem_root_certs = "";
+    ssl_opts.pem_key_cert_pairs.push_back(pkcp);
     creds = grpc::SslServerCredentials(ssl_opts);
   }
   builder.AddListeningPort(server_address.str(), creds);
diff --git a/test/cpp/qps/client.h b/test/cpp/qps/client.h
index 221fb30fc59deac0a8c664baa99160fbe00f1c00..cae7f44537c488ad5b993f456fa9d2c04518fb30 100644
--- a/test/cpp/qps/client.h
+++ b/test/cpp/qps/client.h
@@ -115,12 +115,12 @@ class Client {
           impl_([this, idx, client]() {
             for (;;) {
               // run the loop body
-              client->ThreadFunc(&histogram_, idx);
+	      client->ThreadFunc(&histogram_, idx);
               // lock, see if we're done
               std::lock_guard<std::mutex> g(mu_);
-              if (done_) return;
-              // also check if we're marking, and swap out the histogram if so
-              if (new_) {
+              if (done_) {return;}
+	      // check if we're marking, swap out the histogram if so
+	      if (new_) {
                 new_->Swap(&histogram_);
                 new_ = nullptr;
                 cv_.notify_one();
@@ -164,8 +164,12 @@ class Client {
   std::unique_ptr<Timer> timer_;
 };
 
-std::unique_ptr<Client> CreateSynchronousClient(const ClientConfig& args);
-std::unique_ptr<Client> CreateAsyncClient(const ClientConfig& args);
+std::unique_ptr<Client>
+  CreateSynchronousUnaryClient(const ClientConfig& args);
+std::unique_ptr<Client>
+  CreateSynchronousStreamingClient(const ClientConfig& args);
+std::unique_ptr<Client> CreateAsyncUnaryClient(const ClientConfig& args);
+std::unique_ptr<Client> CreateAsyncStreamingClient(const ClientConfig& args);
 
 }  // namespace testing
 }  // namespace grpc
diff --git a/test/cpp/qps/client_async.cc b/test/cpp/qps/client_async.cc
index 526f37a1fd618a0582bd184b74e652ca798534b1..590d56d8d00d7012b76ca429190f8ab44b11e42b 100644
--- a/test/cpp/qps/client_async.cc
+++ b/test/cpp/qps/client_async.cc
@@ -46,6 +46,7 @@
 #include <grpc++/async_unary_call.h>
 #include <grpc++/client_context.h>
 #include <grpc++/status.h>
+#include <grpc++/stream.h>
 #include "test/core/util/grpc_profiler.h"
 #include "test/cpp/util/create_test_channel.h"
 #include "test/cpp/qps/qpstest.pb.h"
@@ -59,13 +60,13 @@ class ClientRpcContext {
  public:
   ClientRpcContext() {}
   virtual ~ClientRpcContext() {}
-  virtual bool RunNextState() = 0;  // do next state, return false if steps done
+  // next state, return false if done. Collect stats when appropriate
+  virtual bool RunNextState(bool, Histogram* hist) = 0;
   virtual void StartNewClone() = 0;
   static void* tag(ClientRpcContext* c) { return reinterpret_cast<void*>(c); }
   static ClientRpcContext* detag(void* t) {
     return reinterpret_cast<ClientRpcContext*>(t);
   }
-  virtual void report_stats(Histogram* hist) = 0;
 };
 
 template <class RequestType, class ResponseType>
@@ -89,9 +90,12 @@ class ClientRpcContextUnaryImpl : public ClientRpcContext {
         response_reader_(
             start_req(stub_, &context_, req_, ClientRpcContext::tag(this))) {}
   ~ClientRpcContextUnaryImpl() GRPC_OVERRIDE {}
-  bool RunNextState() GRPC_OVERRIDE { return (this->*next_state_)(); }
-  void report_stats(Histogram* hist) GRPC_OVERRIDE {
-    hist->Add((Timer::Now() - start_) * 1e9);
+  bool RunNextState(bool ok, Histogram* hist) GRPC_OVERRIDE {
+    bool ret = (this->*next_state_)(ok);
+    if (!ret) {
+      hist->Add((Timer::Now() - start_) * 1e9);
+    }
+    return ret;
   }
 
   void StartNewClone() GRPC_OVERRIDE {
@@ -99,16 +103,16 @@ class ClientRpcContextUnaryImpl : public ClientRpcContext {
   }
 
  private:
-  bool ReqSent() {
+  bool ReqSent(bool) {
     next_state_ = &ClientRpcContextUnaryImpl::RespDone;
     response_reader_->Finish(&response_, &status_, ClientRpcContext::tag(this));
     return true;
   }
-  bool RespDone() {
+  bool RespDone(bool) {
     next_state_ = &ClientRpcContextUnaryImpl::DoCallBack;
     return false;
   }
-  bool DoCallBack() {
+  bool DoCallBack(bool) {
     callback_(status_, &response_);
     return false;
   }
@@ -116,7 +120,7 @@ class ClientRpcContextUnaryImpl : public ClientRpcContext {
   TestService::Stub* stub_;
   RequestType req_;
   ResponseType response_;
-  bool (ClientRpcContextUnaryImpl::*next_state_)();
+  bool (ClientRpcContextUnaryImpl::*next_state_)(bool);
   std::function<void(grpc::Status, ResponseType*)> callback_;
   std::function<std::unique_ptr<grpc::ClientAsyncResponseReader<ResponseType>>(
       TestService::Stub*, grpc::ClientContext*, const RequestType&, void*)>
@@ -127,9 +131,9 @@ class ClientRpcContextUnaryImpl : public ClientRpcContext {
       response_reader_;
 };
 
-class AsyncClient GRPC_FINAL : public Client {
+class AsyncUnaryClient GRPC_FINAL : public Client {
  public:
-  explicit AsyncClient(const ClientConfig& config) : Client(config) {
+  explicit AsyncUnaryClient(const ClientConfig& config) : Client(config) {
     for (int i = 0; i < config.async_client_threads(); i++) {
       cli_cqs_.emplace_back(new CompletionQueue);
     }
@@ -144,7 +148,8 @@ class AsyncClient GRPC_FINAL : public Client {
 
     int t = 0;
     for (int i = 0; i < config.outstanding_rpcs_per_channel(); i++) {
-      for (auto& channel : channels_) {
+      for (auto channel = channels_.begin(); channel != channels_.end();
+	   channel++) {
         auto* cq = cli_cqs_[t].get();
         t = (t + 1) % cli_cqs_.size();
         auto start_req = [cq](TestService::Stub* stub, grpc::ClientContext* ctx,
@@ -152,7 +157,7 @@ class AsyncClient GRPC_FINAL : public Client {
           return stub->AsyncUnaryCall(ctx, request, cq, tag);
         };
 
-        TestService::Stub* stub = channel.get_stub();
+        TestService::Stub* stub = channel->get_stub();
         const SimpleRequest& request = request_;
         new ClientRpcContextUnaryImpl<SimpleRequest, SimpleResponse>(
             stub, request, start_req, check_done);
@@ -162,14 +167,14 @@ class AsyncClient GRPC_FINAL : public Client {
     StartThreads(config.async_client_threads());
   }
 
-  ~AsyncClient() GRPC_OVERRIDE {
+  ~AsyncUnaryClient() GRPC_OVERRIDE {
     EndThreads();
 
-    for (auto& cq : cli_cqs_) {
-      cq->Shutdown();
+    for (auto cq = cli_cqs_.begin(); cq != cli_cqs_.end(); cq++) {
+      (*cq)->Shutdown();
       void* got_tag;
       bool ok;
-      while (cq->Next(&got_tag, &ok)) {
+      while ((*cq)->Next(&got_tag, &ok)) {
         delete ClientRpcContext::detag(got_tag);
       }
     }
@@ -181,10 +186,9 @@ class AsyncClient GRPC_FINAL : public Client {
     cli_cqs_[thread_idx]->Next(&got_tag, &ok);
 
     ClientRpcContext* ctx = ClientRpcContext::detag(got_tag);
-    if (ctx->RunNextState() == false) {
+    if (ctx->RunNextState(ok, histogram) == false) {
       // call the callback and then delete it
-      ctx->report_stats(histogram);
-      ctx->RunNextState();
+      ctx->RunNextState(ok, histogram);
       ctx->StartNewClone();
       delete ctx;
     }
@@ -193,8 +197,145 @@ class AsyncClient GRPC_FINAL : public Client {
   std::vector<std::unique_ptr<CompletionQueue>> cli_cqs_;
 };
 
-std::unique_ptr<Client> CreateAsyncClient(const ClientConfig& args) {
-  return std::unique_ptr<Client>(new AsyncClient(args));
+template <class RequestType, class ResponseType>
+class ClientRpcContextStreamingImpl : public ClientRpcContext {
+ public:
+  ClientRpcContextStreamingImpl(
+      TestService::Stub *stub, const RequestType &req,
+      std::function<
+              std::unique_ptr<grpc::ClientAsyncReaderWriter<
+                              RequestType,ResponseType>>(
+              TestService::Stub *, grpc::ClientContext *, void *)> start_req,
+      std::function<void(grpc::Status, ResponseType *)> on_done)
+      : context_(),
+        stub_(stub),
+        req_(req),
+        response_(),
+        next_state_(&ClientRpcContextStreamingImpl::ReqSent),
+        callback_(on_done),
+        start_req_(start_req),
+        start_(Timer::Now()),
+        stream_(start_req_(stub_, &context_, ClientRpcContext::tag(this))) {}
+  ~ClientRpcContextStreamingImpl() GRPC_OVERRIDE {}
+  bool RunNextState(bool ok, Histogram *hist) GRPC_OVERRIDE {
+    return (this->*next_state_)(ok, hist);
+  }
+  void StartNewClone() GRPC_OVERRIDE {
+    new ClientRpcContextStreamingImpl(stub_, req_, start_req_, callback_);
+  }
+
+ private:
+  bool ReqSent(bool ok, Histogram *) {
+    return StartWrite(ok);
+  }
+  bool StartWrite(bool ok) {
+    if (!ok) {
+      return(false);
+    }
+    start_ = Timer::Now();
+    next_state_ = &ClientRpcContextStreamingImpl::WriteDone;
+    stream_->Write(req_, ClientRpcContext::tag(this));
+    return true;
+  }
+  bool WriteDone(bool ok, Histogram *) {
+    if (!ok) {
+      return(false);
+    }
+    next_state_ = &ClientRpcContextStreamingImpl::ReadDone;
+    stream_->Read(&response_, ClientRpcContext::tag(this)); 
+    return true;
+  }
+  bool ReadDone(bool ok, Histogram *hist) {
+    hist->Add((Timer::Now() - start_) * 1e9);
+    return StartWrite(ok);
+  }
+  grpc::ClientContext context_;
+  TestService::Stub *stub_;
+  RequestType req_;
+  ResponseType response_;
+  bool (ClientRpcContextStreamingImpl::*next_state_)(bool, Histogram *);
+  std::function<void(grpc::Status, ResponseType *)> callback_;
+  std::function<std::unique_ptr<grpc::ClientAsyncReaderWriter<
+				  RequestType,ResponseType>>(
+      TestService::Stub *, grpc::ClientContext *, void *)> start_req_;
+  grpc::Status status_;
+  double start_;
+  std::unique_ptr<grpc::ClientAsyncReaderWriter<RequestType,ResponseType>>
+    stream_;
+};
+
+class AsyncStreamingClient GRPC_FINAL : public Client {
+ public:
+  explicit AsyncStreamingClient(const ClientConfig &config) : Client(config) {
+    for (int i = 0; i < config.async_client_threads(); i++) {
+      cli_cqs_.emplace_back(new CompletionQueue);
+    }
+
+    auto payload_size = config.payload_size();
+    auto check_done = [payload_size](grpc::Status s, SimpleResponse *response) {
+      GPR_ASSERT(s.IsOk() && (response->payload().type() ==
+                              grpc::testing::PayloadType::COMPRESSABLE) &&
+                 (response->payload().body().length() ==
+                  static_cast<size_t>(payload_size)));
+    };
+
+    int t = 0;
+    for (int i = 0; i < config.outstanding_rpcs_per_channel(); i++) {
+      for (auto channel = channels_.begin(); channel != channels_.end();
+           channel++) {
+        auto* cq = cli_cqs_[t].get();
+        t = (t + 1) % cli_cqs_.size();
+        auto start_req = [cq](TestService::Stub *stub, grpc::ClientContext *ctx,
+                              void *tag) {
+          auto stream = stub->AsyncStreamingCall(ctx, cq, tag);
+          return stream;
+        };
+
+        TestService::Stub *stub = channel->get_stub();
+        const SimpleRequest &request = request_;
+        new ClientRpcContextStreamingImpl<SimpleRequest, SimpleResponse>(
+            stub, request, start_req, check_done);
+      }
+    }
+
+    StartThreads(config.async_client_threads());
+  }
+
+  ~AsyncStreamingClient() GRPC_OVERRIDE {
+    EndThreads();
+
+    for (auto cq = cli_cqs_.begin(); cq != cli_cqs_.end(); cq++) {
+      (*cq)->Shutdown();
+      void *got_tag;
+      bool ok;
+      while ((*cq)->Next(&got_tag, &ok)) {
+        delete ClientRpcContext::detag(got_tag);
+      }
+    }
+  }
+
+  void ThreadFunc(Histogram *histogram, size_t thread_idx) GRPC_OVERRIDE {
+    void *got_tag;
+    bool ok;
+    cli_cqs_[thread_idx]->Next(&got_tag, &ok);
+
+    ClientRpcContext *ctx = ClientRpcContext::detag(got_tag);
+    if (ctx->RunNextState(ok, histogram) == false) {
+      // call the callback and then delete it
+      ctx->RunNextState(ok, histogram);
+      ctx->StartNewClone();
+      delete ctx;
+    }
+  }
+
+  std::vector<std::unique_ptr<CompletionQueue>> cli_cqs_;
+};
+
+std::unique_ptr<Client> CreateAsyncUnaryClient(const ClientConfig& args) {
+  return std::unique_ptr<Client>(new AsyncUnaryClient(args));
+}
+std::unique_ptr<Client> CreateAsyncStreamingClient(const ClientConfig& args) {
+  return std::unique_ptr<Client>(new AsyncStreamingClient(args));
 }
 
 }  // namespace testing
diff --git a/test/cpp/qps/client_sync.cc b/test/cpp/qps/client_sync.cc
index 7bb7231c6f6538bee9542d014a671e12c9c4fc84..e4ee45a72d074a60b5ef895c8e49cde174833a62 100644
--- a/test/cpp/qps/client_sync.cc
+++ b/test/cpp/qps/client_sync.cc
@@ -48,9 +48,11 @@
 #include <grpc/support/host_port.h>
 #include <gflags/gflags.h>
 #include <grpc++/client_context.h>
-#include <grpc++/status.h>
 #include <grpc++/server.h>
 #include <grpc++/server_builder.h>
+#include <grpc++/status.h>
+#include <grpc++/stream.h>
+#include <gtest/gtest.h>
 #include "test/core/util/grpc_profiler.h"
 #include "test/cpp/util/create_test_channel.h"
 #include "test/cpp/qps/client.h"
@@ -61,18 +63,28 @@
 namespace grpc {
 namespace testing {
 
-class SynchronousClient GRPC_FINAL : public Client {
+class SynchronousClient : public Client {
  public:
   SynchronousClient(const ClientConfig& config) : Client(config) {
-    size_t num_threads =
-        config.outstanding_rpcs_per_channel() * config.client_channels();
-    responses_.resize(num_threads);
-    StartThreads(num_threads);
+    num_threads_ =
+      config.outstanding_rpcs_per_channel() * config.client_channels();
+    responses_.resize(num_threads_);
   }
 
-  ~SynchronousClient() { EndThreads(); }
+  virtual ~SynchronousClient() { EndThreads(); }
+
+ protected:
+  size_t num_threads_;
+  std::vector<SimpleResponse> responses_;
+};
 
-  void ThreadFunc(Histogram* histogram, size_t thread_idx) {
+class SynchronousUnaryClient GRPC_FINAL : public SynchronousClient {
+ public:
+  SynchronousUnaryClient(const ClientConfig& config):
+    SynchronousClient(config) {StartThreads(num_threads_);}
+  ~SynchronousUnaryClient() {}
+  
+  void ThreadFunc(Histogram* histogram, size_t thread_idx) GRPC_OVERRIDE {
     auto* stub = channels_[thread_idx % channels_.size()].get_stub();
     double start = Timer::Now();
     grpc::ClientContext context;
@@ -80,13 +92,45 @@ class SynchronousClient GRPC_FINAL : public Client {
         stub->UnaryCall(&context, request_, &responses_[thread_idx]);
     histogram->Add((Timer::Now() - start) * 1e9);
   }
+};
 
- private:
-  std::vector<SimpleResponse> responses_;
+class SynchronousStreamingClient GRPC_FINAL : public SynchronousClient {
+ public:
+  SynchronousStreamingClient(const ClientConfig& config):
+    SynchronousClient(config) {
+    for (size_t thread_idx=0;thread_idx<num_threads_;thread_idx++){
+      auto* stub = channels_[thread_idx % channels_.size()].get_stub();
+      stream_ = stub->StreamingCall(&context_);
+    }
+    StartThreads(num_threads_);
+  }
+  ~SynchronousStreamingClient() {
+    if (stream_) {
+      SimpleResponse response;
+      stream_->WritesDone();
+      EXPECT_TRUE(stream_->Finish().IsOk());
+    }
+  }
+
+  void ThreadFunc(Histogram* histogram, size_t thread_idx) GRPC_OVERRIDE {
+    double start = Timer::Now();
+    EXPECT_TRUE(stream_->Write(request_));
+    EXPECT_TRUE(stream_->Read(&responses_[thread_idx]));
+    histogram->Add((Timer::Now() - start) * 1e9);
+  }
+  private:
+    grpc::ClientContext context_;
+    std::unique_ptr<grpc::ClientReaderWriter<SimpleRequest,
+                                             SimpleResponse>> stream_;
 };
 
-std::unique_ptr<Client> CreateSynchronousClient(const ClientConfig& config) {
-  return std::unique_ptr<Client>(new SynchronousClient(config));
+std::unique_ptr<Client>
+CreateSynchronousUnaryClient(const ClientConfig& config) {
+  return std::unique_ptr<Client>(new SynchronousUnaryClient(config));
+}
+std::unique_ptr<Client>
+CreateSynchronousStreamingClient(const ClientConfig& config) {
+  return std::unique_ptr<Client>(new SynchronousStreamingClient(config));
 }
 
 }  // namespace testing
diff --git a/test/cpp/qps/driver.cc b/test/cpp/qps/driver.cc
index d29ca1de94776b60efc1b5502b9b373a78c98c25..64a53496aed12dcb99286e36e7f35dd2effbada5 100644
--- a/test/cpp/qps/driver.cc
+++ b/test/cpp/qps/driver.cc
@@ -154,19 +154,19 @@ ScenarioResult RunScenario(const ClientConfig& initial_client_config,
   server_mark.mutable_mark();
   ClientArgs client_mark;
   client_mark.mutable_mark();
-  for (auto& server : servers) {
-    GPR_ASSERT(server.stream->Write(server_mark));
+  for (auto server = servers.begin(); server != servers.end(); server++) {
+    GPR_ASSERT(server->stream->Write(server_mark));
   }
-  for (auto& client : clients) {
-    GPR_ASSERT(client.stream->Write(client_mark));
+  for (auto client = clients.begin(); client != clients.end(); client++) {
+    GPR_ASSERT(client->stream->Write(client_mark));
   }
   ServerStatus server_status;
   ClientStatus client_status;
-  for (auto& server : servers) {
-    GPR_ASSERT(server.stream->Read(&server_status));
+  for (auto server = servers.begin(); server != servers.end(); server++) {
+    GPR_ASSERT(server->stream->Read(&server_status));
   }
-  for (auto& client : clients) {
-    GPR_ASSERT(client.stream->Read(&client_status));
+  for (auto client = clients.begin(); client != clients.end(); client++) {
+    GPR_ASSERT(client->stream->Read(&client_status));
   }
 
   // Wait some time
@@ -176,33 +176,33 @@ ScenarioResult RunScenario(const ClientConfig& initial_client_config,
   // Finish a run
   ScenarioResult result;
   gpr_log(GPR_INFO, "Finishing");
-  for (auto& server : servers) {
-    GPR_ASSERT(server.stream->Write(server_mark));
+  for (auto server = servers.begin(); server != servers.end(); server++) {
+    GPR_ASSERT(server->stream->Write(server_mark));
   }
-  for (auto& client : clients) {
-    GPR_ASSERT(client.stream->Write(client_mark));
+  for (auto client = clients.begin(); client != clients.end(); client++) {
+    GPR_ASSERT(client->stream->Write(client_mark));
   }
-  for (auto& server : servers) {
-    GPR_ASSERT(server.stream->Read(&server_status));
+  for (auto server = servers.begin(); server != servers.end(); server++) {
+    GPR_ASSERT(server->stream->Read(&server_status));
     const auto& stats = server_status.stats();
     result.server_resources.push_back(ResourceUsage{
         stats.time_elapsed(), stats.time_user(), stats.time_system()});
   }
-  for (auto& client : clients) {
-    GPR_ASSERT(client.stream->Read(&client_status));
+  for (auto client = clients.begin(); client != clients.end(); client++) {
+    GPR_ASSERT(client->stream->Read(&client_status));
     const auto& stats = client_status.stats();
     result.latencies.MergeProto(stats.latencies());
     result.client_resources.push_back(ResourceUsage{
         stats.time_elapsed(), stats.time_user(), stats.time_system()});
   }
 
-  for (auto& client : clients) {
-    GPR_ASSERT(client.stream->WritesDone());
-    GPR_ASSERT(client.stream->Finish().IsOk());
+  for (auto client = clients.begin(); client != clients.end(); client++) {
+    GPR_ASSERT(client->stream->WritesDone());
+    GPR_ASSERT(client->stream->Finish().IsOk());
   }
-  for (auto& server : servers) {
-    GPR_ASSERT(server.stream->WritesDone());
-    GPR_ASSERT(server.stream->Finish().IsOk());
+  for (auto server = servers.begin(); server != servers.end(); server++) {
+    GPR_ASSERT(server->stream->WritesDone());
+    GPR_ASSERT(server->stream->Finish().IsOk());
   }
   return result;
 }
diff --git a/test/cpp/qps/qps-sweep.sh b/test/cpp/qps/qps-sweep.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7bc6eade2c170cf81db7d2e2b01c109012fc83b6
--- /dev/null
+++ b/test/cpp/qps/qps-sweep.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+if [ x"$QPS_WORKERS" == x ]; then
+  echo Error: Must set QPS_WORKERS variable in form \
+    "host:port,host:port,..." 1>&2
+  exit 1
+fi
+
+bins=`find . .. ../.. ../../.. -name bins | head -1`
+
+for channels in 1 2 4 8
+do
+  for client in SYNCHRONOUS_CLIENT ASYNC_CLIENT
+  do
+    for server in SYNCHRONOUS_SERVER ASYNC_SERVER
+    do
+      for rpc in UNARY STREAMING
+      do
+        echo "Test $rpc $client $server , $channels channels"
+        "$bins"/opt/qps_driver --rpc_type=$rpc \
+          --client_type=$client --server_type=$server
+      done
+    done
+  done
+done
diff --git a/test/cpp/qps/qps_driver.cc b/test/cpp/qps/qps_driver.cc
index 5e9a577f8e6965cd2b023ba685cf1fabdf1ea204..f7aa8e2aba69e9fd6e2d4e32e1810749e976e9fc 100644
--- a/test/cpp/qps/qps_driver.cc
+++ b/test/cpp/qps/qps_driver.cc
@@ -42,6 +42,7 @@ DEFINE_int32(num_servers, 1, "Number of server binaries");
 
 // Common config
 DEFINE_bool(enable_ssl, false, "Use SSL");
+DEFINE_string(rpc_type, "UNARY", "Type of RPC: UNARY or STREAMING");
 
 // Server config
 DEFINE_int32(server_threads, 1, "Number of server threads");
@@ -59,6 +60,7 @@ using grpc::testing::ClientConfig;
 using grpc::testing::ServerConfig;
 using grpc::testing::ClientType;
 using grpc::testing::ServerType;
+using grpc::testing::RpcType;
 using grpc::testing::ResourceUsage;
 using grpc::testing::sum;
 
@@ -73,6 +75,9 @@ int main(int argc, char** argv) {
   grpc_init();
   ParseCommandLineFlags(&argc, &argv, true);
 
+  RpcType rpc_type;
+  GPR_ASSERT(RpcType_Parse(FLAGS_rpc_type, &rpc_type));
+
   ClientType client_type;
   ServerType server_type;
   GPR_ASSERT(ClientType_Parse(FLAGS_client_type, &client_type));
@@ -86,6 +91,7 @@ int main(int argc, char** argv) {
   client_config.set_client_channels(FLAGS_client_channels);
   client_config.set_payload_size(FLAGS_payload_size);
   client_config.set_async_client_threads(FLAGS_async_client_threads);
+  client_config.set_rpc_type(rpc_type);
 
   ServerConfig server_config;
   server_config.set_server_type(server_type);
diff --git a/test/cpp/qps/qpstest.proto b/test/cpp/qps/qpstest.proto
index 6a7170bf5807630fb1fdf4958ddf024b0d94094f..1553ef5f07eac2a15120367a54a83e66dea53c6f 100644
--- a/test/cpp/qps/qpstest.proto
+++ b/test/cpp/qps/qpstest.proto
@@ -87,15 +87,21 @@ enum ServerType {
   ASYNC_SERVER = 2;
 }
 
+enum RpcType {
+  UNARY = 1;
+  STREAMING = 2;
+}
+
 message ClientConfig {
   repeated string server_targets = 1;
   required ClientType client_type = 2;
-  required bool enable_ssl = 3;
+  optional bool enable_ssl = 3 [default=false];
   required int32 outstanding_rpcs_per_channel = 4;
   required int32 client_channels = 5;
   required int32 payload_size = 6;
   // only for async client:
   optional int32 async_client_threads = 7;
+  optional RpcType rpc_type = 8 [default=UNARY];
 }
 
 // Request current stats
@@ -121,8 +127,8 @@ message ClientStatus {
 
 message ServerConfig {
   required ServerType server_type = 1;
-  required int32 threads = 2;
-  required bool enable_ssl = 3;
+  optional int32 threads = 2 [default=1];
+  optional bool enable_ssl = 3 [default=false];
 }
 
 message ServerArgs {
@@ -144,7 +150,7 @@ message SimpleRequest {
 
   // Desired payload size in the response from the server.
   // If response_type is COMPRESSABLE, this denotes the size before compression.
-  optional int32 response_size = 2;
+  optional int32 response_size = 2 [default=0];
 
   // Optional input payload sent along with the request.
   optional Payload payload = 3;
@@ -154,72 +160,14 @@ message SimpleResponse {
   optional Payload payload = 1;
 }
 
-message StreamingInputCallRequest {
-  // Optional input payload sent along with the request.
-  optional Payload payload = 1;
-
-  // Not expecting any payload from the response.
-}
-
-message StreamingInputCallResponse {
-  // Aggregated size of payloads received from the client.
-  optional int32 aggregated_payload_size = 1;
-}
-
-message ResponseParameters {
-  // Desired payload sizes in responses from the server.
-  // If response_type is COMPRESSABLE, this denotes the size before compression.
-  required int32 size = 1;
-
-  // Desired interval between consecutive responses in the response stream in
-  // microseconds.
-  required int32 interval_us = 2;
-}
-
-message StreamingOutputCallRequest {
-  // Desired payload type in the response from the server.
-  // If response_type is RANDOM, the payload from each response in the stream
-  // might be of different types. This is to simulate a mixed type of payload
-  // stream.
-  optional PayloadType response_type = 1 [default=COMPRESSABLE];
-
-  repeated ResponseParameters response_parameters = 2;
-
-  // Optional input payload sent along with the request.
-  optional Payload payload = 3;
-}
-
-message StreamingOutputCallResponse {
-  optional Payload payload = 1;
-}
-
 service TestService {
   // One request followed by one response.
   // The server returns the client payload as-is.
   rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
 
-  // One request followed by a sequence of responses (streamed download).
-  // The server returns the payload with client desired type and sizes.
-  rpc StreamingOutputCall(StreamingOutputCallRequest)
-      returns (stream StreamingOutputCallResponse);
-
-  // A sequence of requests followed by one response (streamed upload).
-  // The server returns the aggregated size of client payload as the result.
-  rpc StreamingInputCall(stream StreamingInputCallRequest)
-      returns (StreamingInputCallResponse);
-
-  // A sequence of requests with each request served by the server immediately.
-  // As one request could lead to multiple responses, this interface
-  // demonstrates the idea of full duplexing.
-  rpc FullDuplexCall(stream StreamingOutputCallRequest)
-      returns (stream StreamingOutputCallResponse);
-
-  // A sequence of requests followed by a sequence of responses.
-  // The server buffers all the client requests and then serves them in order. A
-  // stream of responses are returned to the client when the server starts with
-  // first request.
-  rpc HalfDuplexCall(stream StreamingOutputCallRequest)
-      returns (stream StreamingOutputCallResponse);
+  // One request followed by one response.
+  // The server returns the client payload as-is.
+  rpc StreamingCall(stream SimpleRequest) returns (stream SimpleResponse);
 }
 
 service Worker {
diff --git a/test/cpp/qps/server.cc b/test/cpp/qps/server.cc
index e1907b069b375a4eddd2f73200c74ec603e139cd..258c5e145b8d47b593fa8e314fa655f57e1fa29b 100644
--- a/test/cpp/qps/server.cc
+++ b/test/cpp/qps/server.cc
@@ -115,7 +115,7 @@ class TestServiceImpl GRPC_FINAL : public TestService::Service {
   }
   Status UnaryCall(ServerContext* context, const SimpleRequest* request,
                    SimpleResponse* response) {
-    if (request->has_response_size() && request->response_size() > 0) {
+    if (request->response_size() > 0) {
       if (!SetPayload(request->response_type(), request->response_size(),
                       response->mutable_payload())) {
         return Status(grpc::StatusCode::INTERNAL, "Error creating payload.");
diff --git a/test/cpp/qps/server_async.cc b/test/cpp/qps/server_async.cc
index 586b6e7abee82c8e3371eeb048c1b116fd4ad03a..83bb08cd49872bb05045e8c8c0dab2e7e475e67a 100644
--- a/test/cpp/qps/server_async.cc
+++ b/test/cpp/qps/server_async.cc
@@ -33,6 +33,7 @@
 
 #include <forward_list>
 #include <functional>
+#include <mutex>
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <sys/signal.h>
@@ -48,6 +49,7 @@
 #include <grpc++/server_context.h>
 #include <grpc++/server_credentials.h>
 #include <grpc++/status.h>
+#include <grpc++/stream.h>
 #include <gtest/gtest.h>
 #include "src/cpp/server/thread_pool.h"
 #include "test/core/util/grpc_profiler.h"
@@ -63,7 +65,8 @@ namespace testing {
 class AsyncQpsServerTest : public Server {
  public:
   AsyncQpsServerTest(const ServerConfig& config, int port)
-      : srv_cq_(), async_service_(&srv_cq_), server_(nullptr) {
+      : srv_cq_(), async_service_(&srv_cq_), server_(nullptr),
+        shutdown_(false) {
     char* server_address = NULL;
     gpr_join_host_port(&server_address, "::", port);
 
@@ -78,10 +81,16 @@ class AsyncQpsServerTest : public Server {
     using namespace std::placeholders;
     request_unary_ = std::bind(&TestService::AsyncService::RequestUnaryCall,
                                &async_service_, _1, _2, _3, &srv_cq_, _4);
+    request_streaming_ =
+      std::bind(&TestService::AsyncService::RequestStreamingCall,
+		&async_service_, _1, _2, &srv_cq_, _3);
     for (int i = 0; i < 100; i++) {
       contexts_.push_front(
           new ServerRpcContextUnaryImpl<SimpleRequest, SimpleResponse>(
-              request_unary_, UnaryCall));
+              request_unary_, ProcessRPC));
+      contexts_.push_front(
+          new ServerRpcContextStreamingImpl<SimpleRequest, SimpleResponse>(
+              request_streaming_, ProcessRPC));
     }
     for (int i = 0; i < config.threads(); i++) {
       threads_.push_back(std::thread([=]() {
@@ -89,14 +98,15 @@ class AsyncQpsServerTest : public Server {
         bool ok;
         void* got_tag;
         while (srv_cq_.Next(&got_tag, &ok)) {
-          if (ok) {
-            ServerRpcContext* ctx = detag(got_tag);
-            // The tag is a pointer to an RPC context to invoke
-            if (ctx->RunNextState() == false) {
-              // this RPC context is done, so refresh it
+	  ServerRpcContext* ctx = detag(got_tag);
+	  // The tag is a pointer to an RPC context to invoke
+	  if (ctx->RunNextState(ok) == false) {
+	    // this RPC context is done, so refresh it
+            std::lock_guard<std::mutex> g(shutdown_mutex_);
+            if (!shutdown_) {
               ctx->Reset();
             }
-          }
+	  }
         }
         return;
       }));
@@ -104,9 +114,13 @@ class AsyncQpsServerTest : public Server {
   }
   ~AsyncQpsServerTest() {
     server_->Shutdown();
-    srv_cq_.Shutdown();
-    for (auto& thr : threads_) {
-      thr.join();
+    {
+      std::lock_guard<std::mutex> g(shutdown_mutex_);
+      shutdown_ = true;
+      srv_cq_.Shutdown();
+    }
+    for (auto thr = threads_.begin(); thr != threads_.end(); thr++) {
+      thr->join();
     }
     while (!contexts_.empty()) {
       delete contexts_.front();
@@ -119,7 +133,7 @@ class AsyncQpsServerTest : public Server {
    public:
     ServerRpcContext() {}
     virtual ~ServerRpcContext(){};
-    virtual bool RunNextState() = 0;  // do next state, return false if all done
+    virtual bool RunNextState(bool) = 0;  // next state, return false if done
     virtual void Reset() = 0;         // start this back at a clean state
   };
   static void* tag(ServerRpcContext* func) {
@@ -130,7 +144,7 @@ class AsyncQpsServerTest : public Server {
   }
 
   template <class RequestType, class ResponseType>
-  class ServerRpcContextUnaryImpl : public ServerRpcContext {
+  class ServerRpcContextUnaryImpl GRPC_FINAL : public ServerRpcContext {
    public:
     ServerRpcContextUnaryImpl(
         std::function<void(ServerContext*, RequestType*,
@@ -146,7 +160,7 @@ class AsyncQpsServerTest : public Server {
                       AsyncQpsServerTest::tag(this));
     }
     ~ServerRpcContextUnaryImpl() GRPC_OVERRIDE {}
-    bool RunNextState() GRPC_OVERRIDE { return (this->*next_state_)(); }
+    bool RunNextState(bool ok) GRPC_OVERRIDE {return (this->*next_state_)(ok);}
     void Reset() GRPC_OVERRIDE {
       srv_ctx_ = ServerContext();
       req_ = RequestType();
@@ -160,8 +174,11 @@ class AsyncQpsServerTest : public Server {
     }
 
    private:
-    bool finisher() { return false; }
-    bool invoker() {
+    bool finisher(bool) { return false; }
+    bool invoker(bool ok) {
+      if (!ok)
+	return false;
+
       ResponseType response;
 
       // Call the RPC processing function
@@ -174,7 +191,7 @@ class AsyncQpsServerTest : public Server {
     }
     ServerContext srv_ctx_;
     RequestType req_;
-    bool (ServerRpcContextUnaryImpl::*next_state_)();
+    bool (ServerRpcContextUnaryImpl::*next_state_)(bool);
     std::function<void(ServerContext*, RequestType*,
                        grpc::ServerAsyncResponseWriter<ResponseType>*, void*)>
         request_method_;
@@ -183,9 +200,88 @@ class AsyncQpsServerTest : public Server {
     grpc::ServerAsyncResponseWriter<ResponseType> response_writer_;
   };
 
-  static Status UnaryCall(const SimpleRequest* request,
-                          SimpleResponse* response) {
-    if (request->has_response_size() && request->response_size() > 0) {
+  template <class RequestType, class ResponseType>
+  class ServerRpcContextStreamingImpl GRPC_FINAL : public ServerRpcContext {
+   public:
+    ServerRpcContextStreamingImpl(
+        std::function<void(ServerContext *,
+                           grpc::ServerAsyncReaderWriter<ResponseType,
+			   RequestType> *, void *)> request_method,
+        std::function<grpc::Status(const RequestType *, ResponseType *)>
+            invoke_method)
+        : next_state_(&ServerRpcContextStreamingImpl::request_done),
+          request_method_(request_method),
+          invoke_method_(invoke_method),
+          stream_(&srv_ctx_) {
+      request_method_(&srv_ctx_, &stream_, AsyncQpsServerTest::tag(this));
+    }
+    ~ServerRpcContextStreamingImpl() GRPC_OVERRIDE {
+    }
+    bool RunNextState(bool ok) GRPC_OVERRIDE {return (this->*next_state_)(ok);}
+    void Reset() GRPC_OVERRIDE {
+      srv_ctx_ = ServerContext();
+      req_ = RequestType();
+      stream_ = grpc::ServerAsyncReaderWriter<ResponseType,
+					      RequestType>(&srv_ctx_);
+
+      // Then request the method
+      next_state_ = &ServerRpcContextStreamingImpl::request_done;
+      request_method_(&srv_ctx_, &stream_, AsyncQpsServerTest::tag(this));
+    }
+
+   private:
+    bool request_done(bool ok) {
+      if (!ok)
+	return false;
+      stream_.Read(&req_, AsyncQpsServerTest::tag(this));
+      next_state_ = &ServerRpcContextStreamingImpl::read_done;
+      return true;
+    }
+
+    bool read_done(bool ok) {
+      if (ok) {
+	// invoke the method
+	ResponseType response;
+	// Call the RPC processing function
+	grpc::Status status = invoke_method_(&req_, &response);
+	// initiate the write
+	stream_.Write(response, AsyncQpsServerTest::tag(this));
+	next_state_ = &ServerRpcContextStreamingImpl::write_done;
+      } else {	// client has sent writes done
+	// finish the stream
+	stream_.Finish(Status::OK, AsyncQpsServerTest::tag(this));
+	next_state_ = &ServerRpcContextStreamingImpl::finish_done;
+      }
+      return true;
+    }
+    bool write_done(bool ok) {
+      // now go back and get another streaming read!
+      if (ok) {
+	stream_.Read(&req_, AsyncQpsServerTest::tag(this));
+	next_state_ = &ServerRpcContextStreamingImpl::read_done;
+      }
+      else {
+	stream_.Finish(Status::OK, AsyncQpsServerTest::tag(this));
+	next_state_ = &ServerRpcContextStreamingImpl::finish_done;
+      }
+      return true;
+    }
+    bool finish_done(bool ok) {return false; /* reset the context */ }
+
+    ServerContext srv_ctx_;
+    RequestType req_;
+    bool (ServerRpcContextStreamingImpl::*next_state_)(bool);
+    std::function<void(ServerContext *,
+		       grpc::ServerAsyncReaderWriter<ResponseType,
+		       RequestType> *, void *)> request_method_;
+    std::function<grpc::Status(const RequestType *, ResponseType *)>
+        invoke_method_;
+    grpc::ServerAsyncReaderWriter<ResponseType,RequestType> stream_;
+  };
+
+  static Status ProcessRPC(const SimpleRequest* request,
+			   SimpleResponse* response) {
+    if (request->response_size() > 0) {
       if (!SetPayload(request->response_type(), request->response_size(),
                       response->mutable_payload())) {
         return Status(grpc::StatusCode::INTERNAL, "Error creating payload.");
@@ -200,7 +296,13 @@ class AsyncQpsServerTest : public Server {
   std::function<void(ServerContext*, SimpleRequest*,
                      grpc::ServerAsyncResponseWriter<SimpleResponse>*, void*)>
       request_unary_;
+  std::function<void(ServerContext*, grpc::ServerAsyncReaderWriter<
+		     SimpleResponse,SimpleRequest>*, void*)>
+      request_streaming_;
   std::forward_list<ServerRpcContext*> contexts_;
+
+  std::mutex shutdown_mutex_;
+  bool shutdown_;
 };
 
 std::unique_ptr<Server> CreateAsyncServer(const ServerConfig& config,
diff --git a/test/cpp/qps/server_sync.cc b/test/cpp/qps/server_sync.cc
index 3e15fb61c02c3f1d11ed4ad9db96c327cf684549..6724b8f79af490ee032b960960716d33673375fc 100644
--- a/test/cpp/qps/server_sync.cc
+++ b/test/cpp/qps/server_sync.cc
@@ -62,7 +62,7 @@ class TestServiceImpl GRPC_FINAL : public TestService::Service {
  public:
   Status UnaryCall(ServerContext* context, const SimpleRequest* request,
                    SimpleResponse* response) GRPC_OVERRIDE {
-    if (request->has_response_size() && request->response_size() > 0) {
+    if (request->response_size() > 0) {
       if (!Server::SetPayload(request->response_type(),
                               request->response_size(),
                               response->mutable_payload())) {
@@ -71,6 +71,23 @@ class TestServiceImpl GRPC_FINAL : public TestService::Service {
     }
     return Status::OK;
   }
+  Status StreamingCall(ServerContext *context,
+		       ServerReaderWriter<SimpleResponse, SimpleRequest>*
+		       stream) GRPC_OVERRIDE {
+    SimpleRequest request;
+    while (stream->Read(&request)) {
+      SimpleResponse response;
+      if (request.response_size() > 0) {
+	if (!Server::SetPayload(request.response_type(),
+				request.response_size(),
+				response.mutable_payload())) {
+	  return Status(grpc::StatusCode::INTERNAL, "Error creating payload.");
+	}
+      }
+      stream->Write(response);
+    }
+    return Status::OK;
+  }
 };
 
 class SynchronousServer GRPC_FINAL : public grpc::testing::Server {
diff --git a/test/cpp/qps/stats.h b/test/cpp/qps/stats.h
index ca59390ad79958a371dec9dda4747d8f5ebc65d9..82dc03e3dadf7ed06e3e3a098e57dbf957e8211d 100644
--- a/test/cpp/qps/stats.h
+++ b/test/cpp/qps/stats.h
@@ -43,8 +43,8 @@ namespace testing {
 template <class T, class F>
 double sum(const T& container, F functor) {
   double r = 0;
-  for (auto v : container) {
-    r += functor(v);
+  for (auto v = container.begin(); v != container.end(); v++) {
+    r += functor(*v);
   }
   return r;
 }
diff --git a/test/cpp/qps/timer.cc b/test/cpp/qps/timer.cc
index 3c1342041cf1f01b6c8ef077a010554c1a7ce2e1..d1b6bc1e55a3418114dbda55b9a43035fafa308b 100644
--- a/test/cpp/qps/timer.cc
+++ b/test/cpp/qps/timer.cc
@@ -36,6 +36,7 @@
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <grpc/support/time.h>
+#include <grpc++/config.h>
 
 Timer::Timer() : start_(Sample()) {}
 
diff --git a/test/cpp/qps/worker.cc b/test/cpp/qps/worker.cc
index fdcd9d506975fcc8dd3548f2581dddd2800a9457..dddc4c9850778ceaac4a88f41b055d74c7884cc3 100644
--- a/test/cpp/qps/worker.cc
+++ b/test/cpp/qps/worker.cc
@@ -77,9 +77,12 @@ namespace testing {
 std::unique_ptr<Client> CreateClient(const ClientConfig& config) {
   switch (config.client_type()) {
     case ClientType::SYNCHRONOUS_CLIENT:
-      return CreateSynchronousClient(config);
+      return (config.rpc_type() == RpcType::UNARY) ?
+	CreateSynchronousUnaryClient(config) :
+	CreateSynchronousStreamingClient(config);
     case ClientType::ASYNC_CLIENT:
-      return CreateAsyncClient(config);
+      return (config.rpc_type() == RpcType::UNARY) ?
+	CreateAsyncUnaryClient(config) : CreateAsyncStreamingClient(config);
   }
   abort();
 }
diff --git a/test/cpp/util/cli_call.cc b/test/cpp/util/cli_call.cc
new file mode 100644
index 0000000000000000000000000000000000000000..eb67b8d314bc2e21e11268a1d699402102b2ae10
--- /dev/null
+++ b/test/cpp/util/cli_call.cc
@@ -0,0 +1,106 @@
+/*
+ *
+ * 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/cpp/util/cli_call.h"
+
+#include <iostream>
+
+#include <grpc++/byte_buffer.h>
+#include <grpc++/channel_interface.h>
+#include <grpc++/client_context.h>
+#include <grpc++/generic_stub.h>
+#include <grpc++/status.h>
+#include <grpc++/stream.h>
+
+#include <grpc/grpc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/slice.h>
+
+namespace grpc {
+namespace testing {
+namespace {
+void* tag(int i) { return (void*)(gpr_intptr) i; }
+}  // namespace
+
+void CliCall::Call(std::shared_ptr<grpc::ChannelInterface> channel,
+                   const grpc::string& method, const grpc::string& request,
+                   grpc::string* response) {
+  std::unique_ptr<grpc::GenericStub> stub(new grpc::GenericStub(channel));
+  grpc::ClientContext ctx;
+  grpc::CompletionQueue cq;
+  std::unique_ptr<grpc::GenericClientAsyncReaderWriter> call(
+      stub->Call(&ctx, method, &cq, tag(1)));
+  void* got_tag;
+  bool ok;
+  cq.Next(&got_tag, &ok);
+  GPR_ASSERT(ok);
+
+  gpr_slice s = gpr_slice_from_copied_string(request.c_str());
+  grpc::Slice req_slice(s, grpc::Slice::STEAL_REF);
+  grpc::ByteBuffer send_buffer(&req_slice, 1);
+  call->Write(send_buffer, tag(2));
+  cq.Next(&got_tag, &ok);
+  GPR_ASSERT(ok);
+  call->WritesDone(tag(3));
+  cq.Next(&got_tag, &ok);
+  GPR_ASSERT(ok);
+  grpc::ByteBuffer recv_buffer;
+  call->Read(&recv_buffer, tag(4));
+  cq.Next(&got_tag, &ok);
+  if (!ok) {
+    std::cout << "Failed to read response." << std::endl;
+    return;
+  }
+  grpc::Status status;
+  call->Finish(&status, tag(5));
+  cq.Next(&got_tag, &ok);
+  GPR_ASSERT(ok);
+
+  if (status.IsOk()) {
+    std::cout << "RPC finished with OK status." << std::endl;
+    std::vector<grpc::Slice> slices;
+    recv_buffer.Dump(&slices);
+
+    response->clear();
+    for (size_t i = 0; i < slices.size(); i++) {
+      response->append(reinterpret_cast<const char*>(slices[i].begin()),
+                       slices[i].size());
+    }
+  } else {
+    std::cout << "RPC finished with status code " << status.code()
+              << " details: " << status.details() << std::endl;
+  }
+}
+
+}  // namespace testing
+}  // namespace grpc
diff --git a/src/php/ext/grpc/event.h b/test/cpp/util/cli_call.h
old mode 100755
new mode 100644
similarity index 76%
rename from src/php/ext/grpc/event.h
rename to test/cpp/util/cli_call.h
index ef5846aee1db2bde0c16c4a4bf73f3a678f79eac..7be8bb63c41846651eae2ca94861c618d4e87f73
--- a/src/php/ext/grpc/event.h
+++ b/test/cpp/util/cli_call.h
@@ -31,21 +31,23 @@
  *
  */
 
-#ifndef NET_GRPC_PHP_GRPC_EVENT_H_
-#define NET_GRPC_PHP_GRPC_EVENT_H_
+#ifndef GRPC_TEST_CPP_UTIL_CLI_CALL_H
+#define GRPC_TEST_CPP_UTIL_CLI_CALL_H
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
+#include <grpc++/channel_interface.h>
+#include <grpc++/config.h>
 
-#include "php.h"
-#include "php_ini.h"
-#include "ext/standard/info.h"
-#include "php_grpc.h"
+namespace grpc {
+namespace testing {
 
-#include "grpc/grpc.h"
+class CliCall GRPC_FINAL {
+ public:
+  static void Call(std::shared_ptr<grpc::ChannelInterface> channel,
+                   const grpc::string& method, const grpc::string& request,
+                   grpc::string* response);
+};
 
-/* Create a new Event object that wraps an existing grpc_event struct */
-zval *grpc_php_convert_event(grpc_event *event);
+}  // namespace testing
+}  // namespace grpc
 
-#endif /* NET_GRPC_PHP_GRPC_COMPLETION_CHANNEL_H */
+#endif  // GRPC_TEST_CPP_UTIL_CLI_CALL_H
diff --git a/test/cpp/util/cli_call_test.cc b/test/cpp/util/cli_call_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..91fc40c31f05eb669d892a84c8bc2246538d64b9
--- /dev/null
+++ b/test/cpp/util/cli_call_test.cc
@@ -0,0 +1,131 @@
+/*
+ *
+ * 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/util/test_config.h"
+#include "test/cpp/util/cli_call.h"
+#include "test/cpp/util/echo.pb.h"
+#include "src/cpp/server/thread_pool.h"
+#include <grpc++/channel_arguments.h>
+#include <grpc++/channel_interface.h>
+#include <grpc++/client_context.h>
+#include <grpc++/create_channel.h>
+#include <grpc++/credentials.h>
+#include <grpc++/server.h>
+#include <grpc++/server_builder.h>
+#include <grpc++/server_context.h>
+#include <grpc++/server_credentials.h>
+#include <grpc++/status.h>
+#include "test/core/util/port.h"
+#include <gtest/gtest.h>
+
+#include <grpc/grpc.h>
+
+using grpc::cpp::test::util::EchoRequest;
+using grpc::cpp::test::util::EchoResponse;
+
+namespace grpc {
+namespace testing {
+
+class TestServiceImpl : public ::grpc::cpp::test::util::TestService::Service {
+ public:
+  Status Echo(ServerContext* context, const EchoRequest* request,
+              EchoResponse* response) GRPC_OVERRIDE {
+    response->set_message(request->message());
+    return Status::OK;
+  }
+};
+
+class CliCallTest : public ::testing::Test {
+ protected:
+  CliCallTest() : thread_pool_(2) {}
+
+  void SetUp() GRPC_OVERRIDE {
+    int port = grpc_pick_unused_port_or_die();
+    server_address_ << "localhost:" << port;
+    // Setup server
+    ServerBuilder builder;
+    builder.AddListeningPort(server_address_.str(),
+                             InsecureServerCredentials());
+    builder.RegisterService(&service_);
+    builder.SetThreadPool(&thread_pool_);
+    server_ = builder.BuildAndStart();
+  }
+
+  void TearDown() GRPC_OVERRIDE { server_->Shutdown(); }
+
+  void ResetStub() {
+    channel_ = CreateChannel(server_address_.str(), InsecureCredentials(),
+                             ChannelArguments());
+    stub_ = std::move(grpc::cpp::test::util::TestService::NewStub(channel_));
+  }
+
+  std::shared_ptr<ChannelInterface> channel_;
+  std::unique_ptr<grpc::cpp::test::util::TestService::Stub> stub_;
+  std::unique_ptr<Server> server_;
+  std::ostringstream server_address_;
+  TestServiceImpl service_;
+  ThreadPool thread_pool_;
+};
+
+// Send a rpc with a normal stub and then a CliCall. Verify they match.
+TEST_F(CliCallTest, SimpleRpc) {
+  ResetStub();
+  // Normal stub.
+  EchoRequest request;
+  EchoResponse response;
+  request.set_message("Hello");
+
+  ClientContext context;
+  Status s = stub_->Echo(&context, request, &response);
+  EXPECT_EQ(response.message(), request.message());
+  EXPECT_TRUE(s.IsOk());
+
+  const grpc::string kMethod("/grpc.cpp.test.util.TestService/Echo");
+  grpc::string request_bin, response_bin, expected_response_bin;
+  EXPECT_TRUE(request.SerializeToString(&request_bin));
+  EXPECT_TRUE(response.SerializeToString(&expected_response_bin));
+  CliCall::Call(channel_, kMethod, request_bin, &response_bin);
+  EXPECT_EQ(expected_response_bin, response_bin);
+}
+
+}  // namespace testing
+}  // namespace grpc
+
+int main(int argc, char** argv) {
+  grpc_test_init(argc, argv);
+  grpc_init();
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS();
+  grpc_shutdown();
+  return result;
+}
diff --git a/test/cpp/util/grpc_cli.cc b/test/cpp/util/grpc_cli.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f2271d9365434f7dad355dc3579226b1eb725cbb
--- /dev/null
+++ b/test/cpp/util/grpc_cli.cc
@@ -0,0 +1,139 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+/*
+  A command line tool to talk to any grpc server.
+  Example of talking to grpc interop server:
+  1. Prepare request binary file:
+    a. create a text file input.txt, containing the following:
+        response_size: 10
+        payload: {
+          body: "hello world"
+        }
+    b. under grpc/ run
+        protoc --proto_path=test/cpp/interop/ \
+        --encode=grpc.testing.SimpleRequest test/cpp/interop/messages.proto \
+        < input.txt > input.bin
+  2. Start a server
+    make interop_server && bins/opt/interop_server --port=50051
+  3. Run the tool
+    make grpc_cli && bins/opt/grpc_cli call localhost:50051 \
+    /grpc.testing.TestService/UnaryCall --enable_ssl=false \
+    --input_binary_file=input.bin --output_binary_file=output.bin
+  4. Decode response
+    protoc --proto_path=test/cpp/interop/ \
+    --decode=grpc.testing.SimpleResponse test/cpp/interop/messages.proto \
+    < output.bin > output.txt
+  5. Now the text form of response should be in output.txt
+*/
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <gflags/gflags.h>
+#include "test/cpp/util/cli_call.h"
+#include <grpc++/channel_arguments.h>
+#include <grpc++/channel_interface.h>
+#include <grpc++/create_channel.h>
+#include <grpc++/credentials.h>
+
+#include <grpc/grpc.h>
+
+// In some distros, gflags is in the namespace google, and in some others,
+// in gflags. This hack is enabling us to find both.
+namespace google {}
+namespace gflags {}
+using namespace google;
+using namespace gflags;
+
+DEFINE_bool(enable_ssl, true, "Whether to use ssl/tls.");
+DEFINE_bool(use_auth, false, "Whether to create default google credentials.");
+DEFINE_string(input_binary_file, "",
+              "Path to input file containing serialized request.");
+DEFINE_string(output_binary_file, "output.bin",
+              "Path to output file to write serialized response.");
+
+int main(int argc, char** argv) {
+  grpc_init();
+
+  ParseCommandLineFlags(&argc, &argv, true);
+
+  if (argc < 4 || grpc::string(argv[1]) != "call") {
+    std::cout << "Usage: grpc_cli call server_host:port full_method_string\n"
+              << "Example: grpc_cli call service.googleapis.com "
+              << "/grpc.testing.TestService/UnaryCall "
+              << "--input_binary_file=input.bin --output_binary_file=output.bin"
+              << std::endl;
+  }
+  grpc::string server_address(argv[2]);
+  // TODO(yangg) basic check of method string
+  grpc::string method(argv[3]);
+
+  if (FLAGS_input_binary_file.empty()) {
+    std::cout << "Missing --input_binary_file for serialized request."
+              << std::endl;
+    return 1;
+  }
+  std::cout << "connecting to " << server_address << std::endl;
+
+  std::ifstream input_file(FLAGS_input_binary_file,
+                           std::ios::in | std::ios::binary);
+  std::stringstream input_stream;
+  input_stream << input_file.rdbuf();
+
+  std::unique_ptr<grpc::Credentials> creds;
+  if (!FLAGS_enable_ssl) {
+    creds = grpc::InsecureCredentials();
+  } else {
+    if (FLAGS_use_auth) {
+      creds = grpc::GoogleDefaultCredentials();
+    } else {
+      creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
+    }
+  }
+  std::shared_ptr<grpc::ChannelInterface> channel =
+      grpc::CreateChannel(server_address, creds, grpc::ChannelArguments());
+
+  grpc::string response;
+  grpc::testing::CliCall::Call(channel, method, input_stream.str(), &response);
+  if (!response.empty()) {
+    std::ofstream output_file(FLAGS_output_binary_file,
+                              std::ios::trunc | std::ios::binary);
+    output_file << response;
+  }
+
+  channel.reset();
+  grpc_shutdown();
+  return 0;
+}
diff --git a/tools/dockerfile/grpc_java_base/Dockerfile b/tools/dockerfile/grpc_java_base/Dockerfile
index 2ee0a623c7da5ba913f4968f314cf7273a4adaf8..57b1b90fcd222d41d4828bfbbaffe71269022187 100644
--- a/tools/dockerfile/grpc_java_base/Dockerfile
+++ b/tools/dockerfile/grpc_java_base/Dockerfile
@@ -57,8 +57,6 @@ RUN wget -O - https://github.com/google/protobuf/archive/v3.0.0-alpha-2.tar.gz |
   ./autogen.sh && \
   ./configure --prefix=/usr && \
   make -j12 && make check && make install && \
-  cd java && mvn install && cd .. && \
-  cd javanano && mvn install && cd .. && \
   rm -r "$(pwd)"
 
 # Trigger download of as many Maven and Gradle artifacts as possible. We don't build grpc-java
diff --git a/tools/dockerfile/grpc_php/Dockerfile b/tools/dockerfile/grpc_php/Dockerfile
index 100d7b3bdb33b6559829202e6509c63614ae6c0c..e84455fc94da1a63d2c6f81113087a9eb9b82e65 100644
--- a/tools/dockerfile/grpc_php/Dockerfile
+++ b/tools/dockerfile/grpc_php/Dockerfile
@@ -45,3 +45,5 @@ RUN cd /var/local/git/grpc/src/php/ext/grpc && git pull && phpize
 RUN cd /var/local/git/grpc/src/php/ext/grpc \
   && ./configure \
   && make
+
+RUN cd /var/local/git/grpc/src/php && ./bin/run_tests.sh
\ No newline at end of file
diff --git a/tools/dockerfile/grpc_php_base/Dockerfile b/tools/dockerfile/grpc_php_base/Dockerfile
index cc874fd7c55b308bb9bc69020e85468a0d50652b..c58cccba74291c0f4a3f989976a9d322ef578a07 100644
--- a/tools/dockerfile/grpc_php_base/Dockerfile
+++ b/tools/dockerfile/grpc_php_base/Dockerfile
@@ -50,15 +50,15 @@ RUN apt-get update && apt-get install -y \
     libsqlite3-dev \
     libssl-dev \
     libtool \
+    libxml2 \
     libyaml-dev \
     make \
     patch \
     procps \
-# TODO(mlumish): Uncomment these lines when building against them works
-#    php5-common \
-#    php5-cli \
-#    php5-dev \
-#    php-pear \
+    php5-common \
+    php5-cli \
+    php5-dev \
+    php-pear \
     pkg-config \
     procps \
     sqlite3 \
@@ -66,13 +66,6 @@ RUN apt-get update && apt-get install -y \
 
 # Install the version of PHP gRPC is tested against
 ENV DEBIAN_FRONTEND noniteractive
-RUN apt-get update && apt-get install -y libxml2 libxml2-dev  # used by PHP
-RUN cd /var/local \
-  && curl -o php-5.5.17.tar.gz http://php.net/distributions/php-5.5.17.tar.gz \
-  && tar -xf php-5.5.17.tar.gz \
-  && cd php-5.5.17 \
-  && ./configure --with-zlib=/usr --with-libxml-dir=ext/libxml \
-  && make -j12 && make install
 
 # Download the patched PHP protobuf so that PHP gRPC clients can be generated
 # from proto3 schemas.
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index a132ef4541361618dbf7b1888436e58f01de7f2f..3cf6ddf262369dd1d86831b9decac0143b88d977 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -224,6 +224,24 @@ class CSharpLanguage(object):
   def __str__(self):
     return 'csharp'
 
+class Build(object):
+
+  def test_specs(self, config, travis):
+    return []
+
+  def make_targets(self):
+    return ['all']
+
+  def build_steps(self):
+    return []
+
+  def supports_multi_config(self):
+    return True
+
+  def __str__(self):
+    return self.make_target
+
+
 # different configurations we can run under
 _CONFIGS = {
     'dbg': SimpleConfig('dbg'),
@@ -248,7 +266,8 @@ _LANGUAGES = {
     'php': PhpLanguage(),
     'python': PythonLanguage(),
     'ruby': RubyLanguage(),
-    'csharp': CSharpLanguage()
+    'csharp': CSharpLanguage(),
+    'build': Build(),
     }
 
 # parse command line
diff --git a/tools/run_tests/tests.json b/tools/run_tests/tests.json
index 0150fd4a5d8ec239b9cc188acc8c0fc6494918a3..eab07040e7c73f1ae25f652c5058e6421f5c675d 100644
--- a/tools/run_tests/tests.json
+++ b/tools/run_tests/tests.json
@@ -346,6 +346,11 @@
     "language": "c++", 
     "name": "channel_arguments_test"
   }, 
+  {
+    "flaky": false, 
+    "language": "c++", 
+    "name": "cli_call_test"
+  }, 
   {
     "flaky": false, 
     "language": "c++", 
diff --git a/vsprojects/vs2013/grpc++.vcxproj b/vsprojects/vs2013/grpc++.vcxproj
index 6d921e7353e7e82c20a52c4db7ffe8c6d76355d2..d545a949cb3090e326848d54b88fb7f46a2ddd03 100644
--- a/vsprojects/vs2013/grpc++.vcxproj
+++ b/vsprojects/vs2013/grpc++.vcxproj
@@ -128,6 +128,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\cpp\client\credentials.cc">
     </ClCompile>
+    <ClCompile Include="..\..\src\cpp\client\generic_stub.cc">
+    </ClCompile>
     <ClCompile Include="..\..\src\cpp\client\insecure_credentials.cc">
     </ClCompile>
     <ClCompile Include="..\..\src\cpp\client\internal_stub.cc">
diff --git a/vsprojects/vs2013/grpc++.vcxproj.filters b/vsprojects/vs2013/grpc++.vcxproj.filters
index 61b3bc537f1bbf27092ff0e2558503dc3ce88f73..ed93daee0441d19170e0c6cfb07c202089f107ee 100644
--- a/vsprojects/vs2013/grpc++.vcxproj.filters
+++ b/vsprojects/vs2013/grpc++.vcxproj.filters
@@ -25,6 +25,9 @@
     <ClCompile Include="..\..\src\cpp\client\credentials.cc">
       <Filter>src\cpp\client</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\cpp\client\generic_stub.cc">
+      <Filter>src\cpp\client</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\cpp\client\insecure_credentials.cc">
       <Filter>src\cpp\client</Filter>
     </ClCompile>
diff --git a/vsprojects/vs2013/grpc.vcxproj b/vsprojects/vs2013/grpc.vcxproj
index 754c89985f195f559c9579aeb44ff67872e9924d..a88eb34ab8892a47000ab583260715146b7911d1 100644
--- a/vsprojects/vs2013/grpc.vcxproj
+++ b/vsprojects/vs2013/grpc.vcxproj
@@ -358,6 +358,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\call_details.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\call_log_batch.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel_create.c">
diff --git a/vsprojects/vs2013/grpc.vcxproj.filters b/vsprojects/vs2013/grpc.vcxproj.filters
index 463a770fb8ca8182d0431597dea80004964e2ba2..20dbe8c444f65b7b702adc357d380b352d5c5459 100644
--- a/vsprojects/vs2013/grpc.vcxproj.filters
+++ b/vsprojects/vs2013/grpc.vcxproj.filters
@@ -253,6 +253,9 @@
     <ClCompile Include="..\..\src\core\surface\call_details.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\call_log_batch.c">
+      <Filter>src\core\surface</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
diff --git a/vsprojects/vs2013/grpc_shared.vcxproj b/vsprojects/vs2013/grpc_shared.vcxproj
index 927d2051a2cf379db89598cf2947759885b399bb..b673cc7bded2c44766f5ac076a83fbd8c5529fbe 100644
--- a/vsprojects/vs2013/grpc_shared.vcxproj
+++ b/vsprojects/vs2013/grpc_shared.vcxproj
@@ -362,6 +362,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\call_details.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\call_log_batch.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel_create.c">
diff --git a/vsprojects/vs2013/grpc_shared.vcxproj.filters b/vsprojects/vs2013/grpc_shared.vcxproj.filters
index 463a770fb8ca8182d0431597dea80004964e2ba2..20dbe8c444f65b7b702adc357d380b352d5c5459 100644
--- a/vsprojects/vs2013/grpc_shared.vcxproj.filters
+++ b/vsprojects/vs2013/grpc_shared.vcxproj.filters
@@ -253,6 +253,9 @@
     <ClCompile Include="..\..\src\core\surface\call_details.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\call_log_batch.c">
+      <Filter>src\core\surface</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj b/vsprojects/vs2013/grpc_unsecure.vcxproj
index e1c1bc890b3398133cbf044855ab2cefcdcf3008..98c14c2fdb498dedce20d2cb382ef3fbc15c76ff 100644
--- a/vsprojects/vs2013/grpc_unsecure.vcxproj
+++ b/vsprojects/vs2013/grpc_unsecure.vcxproj
@@ -302,6 +302,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\call_details.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\call_log_batch.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel_create.c">
diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
index fe966237cb2d4e320e966daf64d18de032fd8166..4b758d61132ff7371c8c4874c31359702218fa9e 100644
--- a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
@@ -193,6 +193,9 @@
     <ClCompile Include="..\..\src\core\surface\call_details.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\surface\call_log_batch.c">
+      <Filter>src\core\surface</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\surface\channel.c">
       <Filter>src\core\surface</Filter>
     </ClCompile>