Merge github.com:grpc/grpc

pull/10343/head
Craig Tiller 8 years ago
commit 4fd0abc544
  1. 30
      .vscode/launch.json
  2. 1
      CMakeLists.txt
  3. 4
      Makefile
  4. 6
      WORKSPACE
  5. 1
      build.yaml
  6. 247
      include/grpc++/impl/codegen/async_stream.h
  7. 48
      include/grpc++/impl/codegen/call.h
  8. 15
      include/grpc++/impl/codegen/client_context.h
  9. 106
      include/grpc++/impl/codegen/sync_stream.h
  10. 21
      include/grpc/impl/codegen/grpc_types.h
  11. 67
      src/core/ext/transport/chttp2/transport/chttp2_transport.c
  12. 20
      src/core/ext/transport/chttp2/transport/frame_ping.c
  13. 3
      src/core/ext/transport/chttp2/transport/frame_ping.h
  14. 4
      src/core/ext/transport/chttp2/transport/internal.h
  15. 3
      src/cpp/client/client_context.cc
  16. 58
      src/objective-c/GRPCClient/GRPCCall.m
  17. 61
      src/objective-c/GRPCClient/internal_testing/GRPCCall+InternalTests.h
  18. 52
      src/objective-c/GRPCClient/internal_testing/GRPCCall+InternalTests.m
  19. 59
      src/objective-c/GRPCClient/private/GRPCOpBatchLog.h
  20. 72
      src/objective-c/GRPCClient/private/GRPCOpBatchLog.m
  21. 8
      src/objective-c/GRPCClient/private/GRPCWrappedCall.m
  22. 50
      src/objective-c/RxLibrary/GRXImmediateSingleWriter.h
  23. 89
      src/objective-c/RxLibrary/GRXImmediateSingleWriter.m
  24. 3
      src/objective-c/RxLibrary/GRXWriter+Immediate.m
  25. 1
      src/objective-c/tests/GRPCClientTests.m
  26. 50
      src/objective-c/tests/InteropTests.m
  27. 1
      src/objective-c/tests/InteropTestsLocalCleartext.m
  28. 1
      src/objective-c/tests/InteropTestsLocalSSL.m
  29. 1
      src/objective-c/tests/InteropTestsRemote.m
  30. 4
      src/objective-c/tests/InteropTestsRemoteWithCronet/InteropTestsRemoteWithCronet.m
  31. 7
      src/objective-c/tests/Podfile
  32. 241
      src/objective-c/tests/Tests.xcodeproj/project.pbxproj
  33. 26
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
  34. 6
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalCleartext.xcscheme
  35. 6
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalSSL.xcscheme
  36. 6
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsRemote.xcscheme
  37. 4
      src/objective-c/tests/build_one_example.sh
  38. 20
      src/objective-c/tests/run_tests.sh
  39. BIN
      test/core/end2end/fuzzers/api_fuzzer_corpus/crash-59a56fa18034a104fb9f16cd58071b6ff93b8756
  40. 20
      test/core/end2end/tests/keepalive_timeout.c
  41. 63
      test/core/iomgr/ev_epoll_linux_test.c
  42. 319
      test/cpp/end2end/async_end2end_test.cc
  43. 85
      test/cpp/end2end/end2end_test.cc
  44. 2
      test/cpp/end2end/mock_test.cc
  45. 22
      test/cpp/end2end/test_service_impl.cc
  46. 2
      test/cpp/end2end/test_service_impl.h
  47. 191
      test/cpp/microbenchmarks/bm_fullstack_streaming_ping_pong.cc
  48. 2
      tools/distrib/check_copyright.py
  49. 63
      tools/grpcz/BUILD
  50. 318
      tools/grpcz/census.proto
  51. 181
      tools/grpcz/grpcz_client.cc
  52. 156
      tools/grpcz/monitoring.proto
  53. 1
      tools/run_tests/generated/sources_and_headers.json
  54. 23
      tools/run_tests/generated/tests.json
  55. 2
      vsprojects/vcxproj/test/stress_test/stress_test.vcxproj
  56. 3
      vsprojects/vcxproj/test/stress_test/stress_test.vcxproj.filters

@ -0,0 +1,30 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
},
"runtimeArgs": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceRoot}/src/node/test"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 5858
}
]
}

@ -11363,7 +11363,6 @@ add_executable(stress_test
test/cpp/interop/interop_client.cc test/cpp/interop/interop_client.cc
test/cpp/interop/stress_interop_client.cc test/cpp/interop/stress_interop_client.cc
test/cpp/interop/stress_test.cc test/cpp/interop/stress_test.cc
test/cpp/util/create_test_channel.cc
test/cpp/util/metrics_server.cc test/cpp/util/metrics_server.cc
third_party/googletest/src/gtest-all.cc third_party/googletest/src/gtest-all.cc
) )

@ -15747,7 +15747,6 @@ STRESS_TEST_SRC = \
test/cpp/interop/interop_client.cc \ test/cpp/interop/interop_client.cc \
test/cpp/interop/stress_interop_client.cc \ test/cpp/interop/stress_interop_client.cc \
test/cpp/interop/stress_test.cc \ test/cpp/interop/stress_test.cc \
test/cpp/util/create_test_channel.cc \
test/cpp/util/metrics_server.cc \ test/cpp/util/metrics_server.cc \
STRESS_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(STRESS_TEST_SRC)))) STRESS_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(STRESS_TEST_SRC))))
@ -15793,8 +15792,6 @@ $(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_interop_client.o: $(LIBDIR)/$(CONFI
$(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_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 $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_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 $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
$(OBJDIR)/$(CONFIG)/test/cpp/util/create_test_channel.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 $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
$(OBJDIR)/$(CONFIG)/test/cpp/util/metrics_server.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 $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a $(OBJDIR)/$(CONFIG)/test/cpp/util/metrics_server.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 $(LIBDIR)/$(CONFIG)/libgrpc++_test_config.a
deps_stress_test: $(STRESS_TEST_OBJS:.o=.dep) deps_stress_test: $(STRESS_TEST_OBJS:.o=.dep)
@ -15807,7 +15804,6 @@ endif
$(OBJDIR)/$(CONFIG)/test/cpp/interop/interop_client.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc $(OBJDIR)/$(CONFIG)/test/cpp/interop/interop_client.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
$(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_interop_client.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc $(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_interop_client.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
$(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_test.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc $(OBJDIR)/$(CONFIG)/test/cpp/interop/stress_test.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
$(OBJDIR)/$(CONFIG)/test/cpp/util/create_test_channel.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc
$(OBJDIR)/$(CONFIG)/test/cpp/util/metrics_server.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc $(OBJDIR)/$(CONFIG)/test/cpp/util/metrics_server.o: $(GENDIR)/src/proto/grpc/testing/empty.pb.cc $(GENDIR)/src/proto/grpc/testing/empty.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.pb.cc $(GENDIR)/src/proto/grpc/testing/messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.pb.cc $(GENDIR)/src/proto/grpc/testing/metrics.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/test.pb.cc $(GENDIR)/src/proto/grpc/testing/test.grpc.pb.cc

@ -72,6 +72,12 @@ local_repository(
path = "third_party/gflags", path = "third_party/gflags",
) )
git_repository(
name = "mongoose_repo",
commit = "4120a97945b41195a6223a600dae8e3b19bed19e",
remote = "https://github.com/makdharma/mongoose.git"
)
new_local_repository( new_local_repository(
name = "submodule_cares", name = "submodule_cares",
path = "third_party/cares", path = "third_party/cares",

@ -4136,7 +4136,6 @@ targets:
- test/cpp/interop/interop_client.cc - test/cpp/interop/interop_client.cc
- test/cpp/interop/stress_interop_client.cc - test/cpp/interop/stress_interop_client.cc
- test/cpp/interop/stress_test.cc - test/cpp/interop/stress_test.cc
- test/cpp/util/create_test_channel.cc
- test/cpp/util/metrics_server.cc - test/cpp/util/metrics_server.cc
deps: deps:
- grpc++_test_util - grpc++_test_util

@ -101,6 +101,39 @@ class AsyncWriterInterface {
/// \param[in] msg The message to be written. /// \param[in] msg The message to be written.
/// \param[in] tag The tag identifying the operation. /// \param[in] tag The tag identifying the operation.
virtual void Write(const W& msg, void* tag) = 0; virtual void Write(const W& msg, void* tag) = 0;
/// Request the writing of \a msg using WriteOptions \a options with
/// identifying tag \a tag.
///
/// Only one write may be outstanding at any given time. This means that
/// after calling Write, one must wait to receive \a tag from the completion
/// queue BEFORE calling Write again.
/// WriteOptions \a options is used to set the write options of this message.
/// This is thread-safe with respect to \a Read
///
/// \param[in] msg The message to be written.
/// \param[in] options The WriteOptions to be used to write this message.
/// \param[in] tag The tag identifying the operation.
virtual void Write(const W& msg, WriteOptions options, void* tag) = 0;
/// Request the writing of \a msg and coalesce it with the writing
/// of trailing metadata, using WriteOptions \a options with identifying tag
/// \a tag.
///
/// For client, WriteLast is equivalent of performing Write and WritesDone in
/// a single step.
/// For server, WriteLast buffers the \a msg. The writing of \a msg is held
/// until Finish is called, where \a msg and trailing metadata are coalesced
/// and write is initiated. Note that WriteLast can only buffer \a msg up to
/// the flow control window size. If \a msg size is larger than the window
/// size, it will be sent on wire without buffering.
///
/// \param[in] msg The message to be written.
/// \param[in] options The WriteOptions to be used to write this message.
/// \param[in] tag The tag identifying the operation.
void WriteLast(const W& msg, WriteOptions options, void* tag) {
Write(msg, options.set_last_message(), tag);
}
}; };
template <class R> template <class R>
@ -183,11 +216,17 @@ class ClientAsyncWriter final : public ClientAsyncWriterInterface<W> {
: context_(context), call_(channel->CreateCall(method, context, cq)) { : context_(context), call_(channel->CreateCall(method, context, cq)) {
finish_ops_.RecvMessage(response); finish_ops_.RecvMessage(response);
finish_ops_.AllowNoMessage(); finish_ops_.AllowNoMessage();
// if corked bit is set in context, we buffer up the initial metadata to
init_ops_.set_output_tag(tag); // coalesce with later message to be sent. No op is performed.
init_ops_.SendInitialMetadata(context->send_initial_metadata_, if (context_->initial_metadata_corked_) {
context->initial_metadata_flags()); write_ops_.SendInitialMetadata(context->send_initial_metadata_,
call_.PerformOps(&init_ops_); context->initial_metadata_flags());
} else {
write_ops_.set_output_tag(tag);
write_ops_.SendInitialMetadata(context->send_initial_metadata_,
context->initial_metadata_flags());
call_.PerformOps(&write_ops_);
}
} }
void ReadInitialMetadata(void* tag) override { void ReadInitialMetadata(void* tag) override {
@ -205,10 +244,21 @@ class ClientAsyncWriter final : public ClientAsyncWriterInterface<W> {
call_.PerformOps(&write_ops_); call_.PerformOps(&write_ops_);
} }
void Write(const W& msg, WriteOptions options, void* tag) override {
write_ops_.set_output_tag(tag);
if (options.is_last_message()) {
options.set_buffer_hint();
write_ops_.ClientSendClose();
}
// TODO(ctiller): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg, options).ok());
call_.PerformOps(&write_ops_);
}
void WritesDone(void* tag) override { void WritesDone(void* tag) override {
writes_done_ops_.set_output_tag(tag); write_ops_.set_output_tag(tag);
writes_done_ops_.ClientSendClose(); write_ops_.ClientSendClose();
call_.PerformOps(&writes_done_ops_); call_.PerformOps(&write_ops_);
} }
void Finish(Status* status, void* tag) override { void Finish(Status* status, void* tag) override {
@ -223,10 +273,9 @@ class ClientAsyncWriter final : public ClientAsyncWriterInterface<W> {
private: private:
ClientContext* context_; ClientContext* context_;
Call call_; Call call_;
CallOpSet<CallOpSendInitialMetadata> init_ops_;
CallOpSet<CallOpRecvInitialMetadata> meta_ops_; CallOpSet<CallOpRecvInitialMetadata> meta_ops_;
CallOpSet<CallOpSendMessage> write_ops_; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage, CallOpClientSendClose>
CallOpSet<CallOpClientSendClose> writes_done_ops_; write_ops_;
CallOpSet<CallOpRecvInitialMetadata, CallOpGenericRecvMessage, CallOpSet<CallOpRecvInitialMetadata, CallOpGenericRecvMessage,
CallOpClientRecvStatus> CallOpClientRecvStatus>
finish_ops_; finish_ops_;
@ -253,10 +302,17 @@ class ClientAsyncReaderWriter final
const RpcMethod& method, ClientContext* context, const RpcMethod& method, ClientContext* context,
void* tag) void* tag)
: context_(context), call_(channel->CreateCall(method, context, cq)) { : context_(context), call_(channel->CreateCall(method, context, cq)) {
init_ops_.set_output_tag(tag); if (context_->initial_metadata_corked_) {
init_ops_.SendInitialMetadata(context->send_initial_metadata_, // if corked bit is set in context, we buffer up the initial metadata to
context->initial_metadata_flags()); // coalesce with later message to be sent. No op is performed.
call_.PerformOps(&init_ops_); write_ops_.SendInitialMetadata(context->send_initial_metadata_,
context->initial_metadata_flags());
} else {
write_ops_.set_output_tag(tag);
write_ops_.SendInitialMetadata(context->send_initial_metadata_,
context->initial_metadata_flags());
call_.PerformOps(&write_ops_);
}
} }
void ReadInitialMetadata(void* tag) override { void ReadInitialMetadata(void* tag) override {
@ -283,10 +339,21 @@ class ClientAsyncReaderWriter final
call_.PerformOps(&write_ops_); call_.PerformOps(&write_ops_);
} }
void Write(const W& msg, WriteOptions options, void* tag) override {
write_ops_.set_output_tag(tag);
if (options.is_last_message()) {
options.set_buffer_hint();
write_ops_.ClientSendClose();
}
// TODO(ctiller): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg, options).ok());
call_.PerformOps(&write_ops_);
}
void WritesDone(void* tag) override { void WritesDone(void* tag) override {
writes_done_ops_.set_output_tag(tag); write_ops_.set_output_tag(tag);
writes_done_ops_.ClientSendClose(); write_ops_.ClientSendClose();
call_.PerformOps(&writes_done_ops_); call_.PerformOps(&write_ops_);
} }
void Finish(Status* status, void* tag) override { void Finish(Status* status, void* tag) override {
@ -301,11 +368,10 @@ class ClientAsyncReaderWriter final
private: private:
ClientContext* context_; ClientContext* context_;
Call call_; Call call_;
CallOpSet<CallOpSendInitialMetadata> init_ops_;
CallOpSet<CallOpRecvInitialMetadata> meta_ops_; CallOpSet<CallOpRecvInitialMetadata> meta_ops_;
CallOpSet<CallOpRecvInitialMetadata, CallOpRecvMessage<R>> read_ops_; CallOpSet<CallOpRecvInitialMetadata, CallOpRecvMessage<R>> read_ops_;
CallOpSet<CallOpSendMessage> write_ops_; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage, CallOpClientSendClose>
CallOpSet<CallOpClientSendClose> writes_done_ops_; write_ops_;
CallOpSet<CallOpRecvInitialMetadata, CallOpClientRecvStatus> finish_ops_; CallOpSet<CallOpRecvInitialMetadata, CallOpClientRecvStatus> finish_ops_;
}; };
@ -395,6 +461,20 @@ class ServerAsyncWriterInterface : public ServerAsyncStreamingInterface,
public AsyncWriterInterface<W> { public AsyncWriterInterface<W> {
public: public:
virtual void Finish(const Status& status, void* tag) = 0; virtual void Finish(const Status& status, void* tag) = 0;
/// Request the writing of \a msg and coalesce it with trailing metadata which
/// contains \a status, using WriteOptions options with identifying tag \a
/// tag.
///
/// WriteAndFinish is equivalent of performing WriteLast and Finish in a
/// single step.
///
/// \param[in] msg The message to be written.
/// \param[in] options The WriteOptions to be used to write this message.
/// \param[in] status The Status that server returns to client.
/// \param[in] tag The tag identifying the operation.
virtual void WriteAndFinish(const W& msg, WriteOptions options,
const Status& status, void* tag) = 0;
}; };
template <class W> template <class W>
@ -418,29 +498,37 @@ class ServerAsyncWriter final : public ServerAsyncWriterInterface<W> {
void Write(const W& msg, void* tag) override { void Write(const W& msg, void* tag) override {
write_ops_.set_output_tag(tag); write_ops_.set_output_tag(tag);
if (!ctx_->sent_initial_metadata_) { EnsureInitialMetadataSent(&write_ops_);
write_ops_.SendInitialMetadata(ctx_->initial_metadata_,
ctx_->initial_metadata_flags());
if (ctx_->compression_level_set()) {
write_ops_.set_compression_level(ctx_->compression_level());
}
ctx_->sent_initial_metadata_ = true;
}
// TODO(ctiller): don't assert // TODO(ctiller): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg).ok()); GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg).ok());
call_.PerformOps(&write_ops_); call_.PerformOps(&write_ops_);
} }
void Write(const W& msg, WriteOptions options, void* tag) override {
write_ops_.set_output_tag(tag);
if (options.is_last_message()) {
options.set_buffer_hint();
}
EnsureInitialMetadataSent(&write_ops_);
// TODO(ctiller): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg, options).ok());
call_.PerformOps(&write_ops_);
}
void WriteAndFinish(const W& msg, WriteOptions options, const Status& status,
void* tag) override {
write_ops_.set_output_tag(tag);
EnsureInitialMetadataSent(&write_ops_);
options.set_buffer_hint();
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg, options).ok());
write_ops_.ServerSendStatus(ctx_->trailing_metadata_, status);
call_.PerformOps(&write_ops_);
}
void Finish(const Status& status, void* tag) override { void Finish(const Status& status, void* tag) override {
finish_ops_.set_output_tag(tag); finish_ops_.set_output_tag(tag);
if (!ctx_->sent_initial_metadata_) { EnsureInitialMetadataSent(&finish_ops_);
finish_ops_.SendInitialMetadata(ctx_->initial_metadata_,
ctx_->initial_metadata_flags());
if (ctx_->compression_level_set()) {
finish_ops_.set_compression_level(ctx_->compression_level());
}
ctx_->sent_initial_metadata_ = true;
}
finish_ops_.ServerSendStatus(ctx_->trailing_metadata_, status); finish_ops_.ServerSendStatus(ctx_->trailing_metadata_, status);
call_.PerformOps(&finish_ops_); call_.PerformOps(&finish_ops_);
} }
@ -448,10 +536,24 @@ class ServerAsyncWriter final : public ServerAsyncWriterInterface<W> {
private: private:
void BindCall(Call* call) override { call_ = *call; } void BindCall(Call* call) override { call_ = *call; }
template <class T>
void EnsureInitialMetadataSent(T* ops) {
if (!ctx_->sent_initial_metadata_) {
ops->SendInitialMetadata(ctx_->initial_metadata_,
ctx_->initial_metadata_flags());
if (ctx_->compression_level_set()) {
ops->set_compression_level(ctx_->compression_level());
}
ctx_->sent_initial_metadata_ = true;
}
}
Call call_; Call call_;
ServerContext* ctx_; ServerContext* ctx_;
CallOpSet<CallOpSendInitialMetadata> meta_ops_; CallOpSet<CallOpSendInitialMetadata> meta_ops_;
CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage> write_ops_; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage,
CallOpServerSendStatus>
write_ops_;
CallOpSet<CallOpSendInitialMetadata, CallOpServerSendStatus> finish_ops_; CallOpSet<CallOpSendInitialMetadata, CallOpServerSendStatus> finish_ops_;
}; };
@ -462,6 +564,20 @@ class ServerAsyncReaderWriterInterface : public ServerAsyncStreamingInterface,
public AsyncReaderInterface<R> { public AsyncReaderInterface<R> {
public: public:
virtual void Finish(const Status& status, void* tag) = 0; virtual void Finish(const Status& status, void* tag) = 0;
/// Request the writing of \a msg and coalesce it with trailing metadata which
/// contains \a status, using WriteOptions options with identifying tag \a
/// tag.
///
/// WriteAndFinish is equivalent of performing WriteLast and Finish in a
/// single step.
///
/// \param[in] msg The message to be written.
/// \param[in] options The WriteOptions to be used to write this message.
/// \param[in] status The Status that server returns to client.
/// \param[in] tag The tag identifying the operation.
virtual void WriteAndFinish(const W& msg, WriteOptions options,
const Status& status, void* tag) = 0;
}; };
template <class W, class R> template <class W, class R>
@ -492,29 +608,36 @@ class ServerAsyncReaderWriter final
void Write(const W& msg, void* tag) override { void Write(const W& msg, void* tag) override {
write_ops_.set_output_tag(tag); write_ops_.set_output_tag(tag);
if (!ctx_->sent_initial_metadata_) { EnsureInitialMetadataSent(&write_ops_);
write_ops_.SendInitialMetadata(ctx_->initial_metadata_,
ctx_->initial_metadata_flags());
if (ctx_->compression_level_set()) {
write_ops_.set_compression_level(ctx_->compression_level());
}
ctx_->sent_initial_metadata_ = true;
}
// TODO(ctiller): don't assert // TODO(ctiller): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg).ok()); GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg).ok());
call_.PerformOps(&write_ops_); call_.PerformOps(&write_ops_);
} }
void Write(const W& msg, WriteOptions options, void* tag) override {
write_ops_.set_output_tag(tag);
if (options.is_last_message()) {
options.set_buffer_hint();
}
EnsureInitialMetadataSent(&write_ops_);
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg, options).ok());
call_.PerformOps(&write_ops_);
}
void WriteAndFinish(const W& msg, WriteOptions options, const Status& status,
void* tag) override {
write_ops_.set_output_tag(tag);
EnsureInitialMetadataSent(&write_ops_);
options.set_buffer_hint();
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(msg, options).ok());
write_ops_.ServerSendStatus(ctx_->trailing_metadata_, status);
call_.PerformOps(&write_ops_);
}
void Finish(const Status& status, void* tag) override { void Finish(const Status& status, void* tag) override {
finish_ops_.set_output_tag(tag); finish_ops_.set_output_tag(tag);
if (!ctx_->sent_initial_metadata_) { EnsureInitialMetadataSent(&finish_ops_);
finish_ops_.SendInitialMetadata(ctx_->initial_metadata_,
ctx_->initial_metadata_flags());
if (ctx_->compression_level_set()) {
finish_ops_.set_compression_level(ctx_->compression_level());
}
ctx_->sent_initial_metadata_ = true;
}
finish_ops_.ServerSendStatus(ctx_->trailing_metadata_, status); finish_ops_.ServerSendStatus(ctx_->trailing_metadata_, status);
call_.PerformOps(&finish_ops_); call_.PerformOps(&finish_ops_);
} }
@ -524,11 +647,25 @@ class ServerAsyncReaderWriter final
void BindCall(Call* call) override { call_ = *call; } void BindCall(Call* call) override { call_ = *call; }
template <class T>
void EnsureInitialMetadataSent(T* ops) {
if (!ctx_->sent_initial_metadata_) {
ops->SendInitialMetadata(ctx_->initial_metadata_,
ctx_->initial_metadata_flags());
if (ctx_->compression_level_set()) {
ops->set_compression_level(ctx_->compression_level());
}
ctx_->sent_initial_metadata_ = true;
}
}
Call call_; Call call_;
ServerContext* ctx_; ServerContext* ctx_;
CallOpSet<CallOpSendInitialMetadata> meta_ops_; CallOpSet<CallOpSendInitialMetadata> meta_ops_;
CallOpSet<CallOpRecvMessage<R>> read_ops_; CallOpSet<CallOpRecvMessage<R>> read_ops_;
CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage> write_ops_; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage,
CallOpServerSendStatus>
write_ops_;
CallOpSet<CallOpSendInitialMetadata, CallOpServerSendStatus> finish_ops_; CallOpSet<CallOpSendInitialMetadata, CallOpServerSendStatus> finish_ops_;
}; };

@ -84,8 +84,9 @@ inline grpc_metadata* FillMetadataArray(
/// Per-message write options. /// Per-message write options.
class WriteOptions { class WriteOptions {
public: public:
WriteOptions() : flags_(0) {} WriteOptions() : flags_(0), last_message_(false) {}
WriteOptions(const WriteOptions& other) : flags_(other.flags_) {} WriteOptions(const WriteOptions& other)
: flags_(other.flags_), last_message_(other.last_message_) {}
/// Clear all flags. /// Clear all flags.
inline void Clear() { flags_ = 0; } inline void Clear() { flags_ = 0; }
@ -141,6 +142,43 @@ class WriteOptions {
/// \sa GRPC_WRITE_BUFFER_HINT /// \sa GRPC_WRITE_BUFFER_HINT
inline bool get_buffer_hint() const { return GetBit(GRPC_WRITE_BUFFER_HINT); } inline bool get_buffer_hint() const { return GetBit(GRPC_WRITE_BUFFER_HINT); }
/// corked bit: aliases set_buffer_hint currently, with the intent that
/// set_buffer_hint will be removed in the future
inline WriteOptions& set_corked() {
SetBit(GRPC_WRITE_BUFFER_HINT);
return *this;
}
inline WriteOptions& clear_corked() {
ClearBit(GRPC_WRITE_BUFFER_HINT);
return *this;
}
inline bool is_corked() const { return GetBit(GRPC_WRITE_BUFFER_HINT); }
/// last-message bit: indicates this is the last message in a stream
/// client-side: makes Write the equivalent of performing Write, WritesDone
/// in a single step
/// server-side: hold the Write until the service handler returns (sync api)
/// or until Finish is called (async api)
inline WriteOptions& set_last_message() {
last_message_ = true;
return *this;
}
/// Clears flag indicating that this is the last message in a stream,
/// disabling coalescing.
inline WriteOptions& clear_last_messsage() {
last_message_ = false;
return *this;
}
/// Get value for the flag indicating that this is the last message, and
/// should be coalesced with trailing metadata.
///
/// \sa GRPC_WRITE_LAST_MESSAGE
bool is_last_message() const { return last_message_; }
WriteOptions& operator=(const WriteOptions& rhs) { WriteOptions& operator=(const WriteOptions& rhs) {
flags_ = rhs.flags_; flags_ = rhs.flags_;
return *this; return *this;
@ -154,6 +192,7 @@ class WriteOptions {
bool GetBit(const uint32_t mask) const { return (flags_ & mask) != 0; } bool GetBit(const uint32_t mask) const { return (flags_ & mask) != 0; }
uint32_t flags_; uint32_t flags_;
bool last_message_;
}; };
/// Default argument for CallOpSet. I is unused by the class, but can be /// Default argument for CallOpSet. I is unused by the class, but can be
@ -224,7 +263,7 @@ class CallOpSendMessage {
/// after use. /// after use.
template <class M> template <class M>
Status SendMessage(const M& message, Status SendMessage(const M& message,
const WriteOptions& options) GRPC_MUST_USE_RESULT; WriteOptions options) GRPC_MUST_USE_RESULT;
template <class M> template <class M>
Status SendMessage(const M& message) GRPC_MUST_USE_RESULT; Status SendMessage(const M& message) GRPC_MUST_USE_RESULT;
@ -252,8 +291,7 @@ class CallOpSendMessage {
}; };
template <class M> template <class M>
Status CallOpSendMessage::SendMessage(const M& message, Status CallOpSendMessage::SendMessage(const M& message, WriteOptions options) {
const WriteOptions& options) {
write_options_ = options; write_options_ = options;
return SerializationTraits<M>::Serialize(message, &send_buf_, &own_buf_); return SerializationTraits<M>::Serialize(message, &send_buf_, &own_buf_);
} }

@ -281,6 +281,17 @@ class ClientContext {
/// \param algorithm The compression algorithm used for the client call. /// \param algorithm The compression algorithm used for the client call.
void set_compression_algorithm(grpc_compression_algorithm algorithm); void set_compression_algorithm(grpc_compression_algorithm algorithm);
/// Flag whether the initial metadata should be \a corked
///
/// If \a corked is true, then the initial metadata will be colasced with the
/// write of first message in the stream.
///
/// \param corked The flag indicating whether the initial metadata is to be
/// corked or not.
void set_initial_metadata_corked(bool corked) {
initial_metadata_corked_ = corked;
}
/// Return the peer uri in a string. /// Return the peer uri in a string.
/// ///
/// \warning This value is never authenticated or subject to any security /// \warning This value is never authenticated or subject to any security
@ -357,7 +368,8 @@ class ClientContext {
(cacheable_ ? GRPC_INITIAL_METADATA_CACHEABLE_REQUEST : 0) | (cacheable_ ? GRPC_INITIAL_METADATA_CACHEABLE_REQUEST : 0) |
(wait_for_ready_explicitly_set_ (wait_for_ready_explicitly_set_
? GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET ? GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET
: 0); : 0) |
(initial_metadata_corked_ ? GRPC_INITIAL_METADATA_CORKED : 0);
} }
grpc::string authority() { return authority_; } grpc::string authority() { return authority_; }
@ -384,6 +396,7 @@ class ClientContext {
PropagationOptions propagation_options_; PropagationOptions propagation_options_;
grpc_compression_algorithm compression_algorithm_; grpc_compression_algorithm compression_algorithm_;
bool initial_metadata_corked_;
}; };
} // namespace grpc } // namespace grpc

@ -100,22 +100,40 @@ class WriterInterface {
public: public:
virtual ~WriterInterface() {} virtual ~WriterInterface() {}
/// Blocking write \a msg to the stream with options. /// Blocking write \a msg to the stream with WriteOptions \a options.
/// This is thread-safe with respect to \a Read /// This is thread-safe with respect to \a Read
/// ///
/// \param msg The message to be written to the stream. /// \param msg The message to be written to the stream.
/// \param options Options affecting the write operation. /// \param options The WriteOptions affecting the write operation.
/// ///
/// \return \a true on success, \a false when the stream has been closed. /// \return \a true on success, \a false when the stream has been closed.
virtual bool Write(const W& msg, const WriteOptions& options) = 0; virtual bool Write(const W& msg, WriteOptions options) = 0;
/// Blocking write \a msg to the stream with default options. /// Blocking write \a msg to the stream with default write options.
/// This is thread-safe with respect to \a Read /// This is thread-safe with respect to \a Read
/// ///
/// \param msg The message to be written to the stream. /// \param msg The message to be written to the stream.
/// ///
/// \return \a true on success, \a false when the stream has been closed. /// \return \a true on success, \a false when the stream has been closed.
inline bool Write(const W& msg) { return Write(msg, WriteOptions()); } inline bool Write(const W& msg) { return Write(msg, WriteOptions()); }
/// Write \a msg and coalesce it with the writing of trailing metadata, using
/// WriteOptions \a options.
///
/// For client, WriteLast is equivalent of performing Write and WritesDone in
/// a single step. \a msg and trailing metadata are coalesced and sent on wire
/// by calling this function.
/// For server, WriteLast buffers the \a msg. The writing of \a msg is held
/// until the service handler returns, where \a msg and trailing metadata are
/// coalesced and sent on wire. Note that WriteLast can only buffer \a msg up
/// to the flow control window size. If \a msg size is larger than the window
/// size, it will be sent on wire without buffering.
///
/// \param[in] msg The message to be written to the stream.
/// \param[in] options The WriteOptions to be used to write this message.
void WriteLast(const W& msg, WriteOptions options) {
Write(msg, options.set_last_message());
}
}; };
/// Client-side interface for streaming reads of message of type \a R. /// Client-side interface for streaming reads of message of type \a R.
@ -213,11 +231,13 @@ class ClientWriter : public ClientWriterInterface<W> {
finish_ops_.RecvMessage(response); finish_ops_.RecvMessage(response);
finish_ops_.AllowNoMessage(); finish_ops_.AllowNoMessage();
CallOpSet<CallOpSendInitialMetadata> ops; if (!context_->initial_metadata_corked_) {
ops.SendInitialMetadata(context->send_initial_metadata_, CallOpSet<CallOpSendInitialMetadata> ops;
context->initial_metadata_flags()); ops.SendInitialMetadata(context->send_initial_metadata_,
call_.PerformOps(&ops); context->initial_metadata_flags());
cq_.Pluck(&ops); call_.PerformOps(&ops);
cq_.Pluck(&ops);
}
} }
void WaitForInitialMetadata() { void WaitForInitialMetadata() {
@ -230,11 +250,24 @@ class ClientWriter : public ClientWriterInterface<W> {
} }
using WriterInterface<W>::Write; using WriterInterface<W>::Write;
bool Write(const W& msg, const WriteOptions& options) override { bool Write(const W& msg, WriteOptions options) override {
CallOpSet<CallOpSendMessage> ops; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage,
CallOpClientSendClose>
ops;
if (options.is_last_message()) {
options.set_buffer_hint();
ops.ClientSendClose();
}
if (context_->initial_metadata_corked_) {
ops.SendInitialMetadata(context_->send_initial_metadata_,
context_->initial_metadata_flags());
context_->set_initial_metadata_corked(false);
}
if (!ops.SendMessage(msg, options).ok()) { if (!ops.SendMessage(msg, options).ok()) {
return false; return false;
} }
call_.PerformOps(&ops); call_.PerformOps(&ops);
return cq_.Pluck(&ops); return cq_.Pluck(&ops);
} }
@ -293,11 +326,13 @@ class ClientReaderWriter final : public ClientReaderWriterInterface<W, R> {
ClientReaderWriter(ChannelInterface* channel, const RpcMethod& method, ClientReaderWriter(ChannelInterface* channel, const RpcMethod& method,
ClientContext* context) ClientContext* context)
: context_(context), call_(channel->CreateCall(method, context, &cq_)) { : context_(context), call_(channel->CreateCall(method, context, &cq_)) {
CallOpSet<CallOpSendInitialMetadata> ops; if (!context_->initial_metadata_corked_) {
ops.SendInitialMetadata(context->send_initial_metadata_, CallOpSet<CallOpSendInitialMetadata> ops;
context->initial_metadata_flags()); ops.SendInitialMetadata(context->send_initial_metadata_,
call_.PerformOps(&ops); context->initial_metadata_flags());
cq_.Pluck(&ops); call_.PerformOps(&ops);
cq_.Pluck(&ops);
}
} }
void WaitForInitialMetadata() override { void WaitForInitialMetadata() override {
@ -325,9 +360,24 @@ class ClientReaderWriter final : public ClientReaderWriterInterface<W, R> {
} }
using WriterInterface<W>::Write; using WriterInterface<W>::Write;
bool Write(const W& msg, const WriteOptions& options) override { bool Write(const W& msg, WriteOptions options) override {
CallOpSet<CallOpSendMessage> ops; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage,
if (!ops.SendMessage(msg, options).ok()) return false; CallOpClientSendClose>
ops;
if (options.is_last_message()) {
options.set_buffer_hint();
ops.ClientSendClose();
}
if (context_->initial_metadata_corked_) {
ops.SendInitialMetadata(context_->send_initial_metadata_,
context_->initial_metadata_flags());
context_->set_initial_metadata_corked(false);
}
if (!ops.SendMessage(msg, options).ok()) {
return false;
}
call_.PerformOps(&ops); call_.PerformOps(&ops);
return cq_.Pluck(&ops); return cq_.Pluck(&ops);
} }
@ -423,7 +473,10 @@ class ServerWriter final : public ServerWriterInterface<W> {
} }
using WriterInterface<W>::Write; using WriterInterface<W>::Write;
bool Write(const W& msg, const WriteOptions& options) override { bool Write(const W& msg, WriteOptions options) override {
if (options.is_last_message()) {
options.set_buffer_hint();
}
CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage> ops; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage> ops;
if (!ops.SendMessage(msg, options).ok()) { if (!ops.SendMessage(msg, options).ok()) {
return false; return false;
@ -485,7 +538,10 @@ class ServerReaderWriterBody final {
return call_->cq()->Pluck(&ops) && ops.got_message; return call_->cq()->Pluck(&ops) && ops.got_message;
} }
bool Write(const W& msg, const WriteOptions& options) { bool Write(const W& msg, WriteOptions options) {
if (options.is_last_message()) {
options.set_buffer_hint();
}
CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage> ops; CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage> ops;
if (!ops.SendMessage(msg, options).ok()) { if (!ops.SendMessage(msg, options).ok()) {
return false; return false;
@ -523,7 +579,7 @@ class ServerReaderWriter final : public ServerReaderWriterInterface<W, R> {
bool Read(R* msg) override { return body_.Read(msg); } bool Read(R* msg) override { return body_.Read(msg); }
using WriterInterface<W>::Write; using WriterInterface<W>::Write;
bool Write(const W& msg, const WriteOptions& options) override { bool Write(const W& msg, WriteOptions options) override {
return body_.Write(msg, options); return body_.Write(msg, options);
} }
@ -562,8 +618,7 @@ class ServerUnaryStreamer final
} }
using WriterInterface<ResponseType>::Write; using WriterInterface<ResponseType>::Write;
bool Write(const ResponseType& response, bool Write(const ResponseType& response, WriteOptions options) override {
const WriteOptions& options) override {
if (write_done_ || !read_done_) { if (write_done_ || !read_done_) {
return false; return false;
} }
@ -604,8 +659,7 @@ class ServerSplitStreamer final
} }
using WriterInterface<ResponseType>::Write; using WriterInterface<ResponseType>::Write;
bool Write(const ResponseType& response, bool Write(const ResponseType& response, WriteOptions options) override {
const WriteOptions& options) override {
return read_done_ && body_.Write(response, options); return read_done_ && body_.Write(response, options);
} }

@ -198,14 +198,14 @@ typedef struct {
#define GRPC_ARG_HTTP2_WRITE_BUFFER_SIZE "grpc.http2.write_buffer_size" #define GRPC_ARG_HTTP2_WRITE_BUFFER_SIZE "grpc.http2.write_buffer_size"
/** After a duration of this time the client pings the server to see if the /** After a duration of this time the client pings the server to see if the
transport is still alive. Int valued, seconds. */ transport is still alive. Int valued, seconds. */
#define GRPC_ARG_HTTP2_KEEPALIVE_TIME "grpc.http2.keepalive_time" #define GRPC_ARG_CLIENT_KEEPALIVE_TIME_S "grpc.client_keepalive_time"
/** After waiting for a duration of this time, if the client does not receive /** After waiting for a duration of this time, if the client does not receive
the ping ack, it will close the transport. Int valued, seconds. */ the ping ack, it will close the transport. Int valued, seconds. */
#define GRPC_ARG_HTTP2_KEEPALIVE_TIMEOUT "grpc.http2.keepalive_timeout" #define GRPC_ARG_CLIENT_KEEPALIVE_TIMEOUT_S "grpc.client_keepalive_timeout"
/** Is it permissible to send keepalive pings without any outstanding streams. /** Is it permissible to send keepalive pings without any outstanding streams.
Int valued, 0(false)/1(true). */ Int valued, 0(false)/1(true). */
#define GRPC_ARG_HTTP2_KEEPALIVE_PERMIT_WITHOUT_CALLS \ #define GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS \
"grpc.http2.keepalive_permit_without_calls" "grpc.keepalive_permit_without_calls"
/** Default authority to pass if none specified on call construction. A string. /** Default authority to pass if none specified on call construction. A string.
* */ * */
#define GRPC_ARG_DEFAULT_AUTHORITY "grpc.default_authority" #define GRPC_ARG_DEFAULT_AUTHORITY "grpc.default_authority"
@ -316,13 +316,16 @@ typedef enum grpc_call_error {
/** Signal that GRPC_INITIAL_METADATA_WAIT_FOR_READY was explicitly set /** Signal that GRPC_INITIAL_METADATA_WAIT_FOR_READY was explicitly set
by the calling application. */ by the calling application. */
#define GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET (0x00000080u) #define GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET (0x00000080u)
/** Signal that the initial metadata should be corked */
#define GRPC_INITIAL_METADATA_CORKED (0x00000100u)
/** Mask of all valid flags */ /** Mask of all valid flags */
#define GRPC_INITIAL_METADATA_USED_MASK \ #define GRPC_INITIAL_METADATA_USED_MASK \
(GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST | \ (GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST | \
GRPC_INITIAL_METADATA_WAIT_FOR_READY | \ GRPC_INITIAL_METADATA_WAIT_FOR_READY | \
GRPC_INITIAL_METADATA_CACHEABLE_REQUEST | \ GRPC_INITIAL_METADATA_CACHEABLE_REQUEST | \
GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET) GRPC_INITIAL_METADATA_WAIT_FOR_READY_EXPLICITLY_SET | \
GRPC_INITIAL_METADATA_CORKED)
/** A single metadata element */ /** A single metadata element */
typedef struct grpc_metadata { typedef struct grpc_metadata {

@ -69,10 +69,16 @@
#define MAX_WRITE_BUFFER_SIZE (64 * 1024 * 1024) #define MAX_WRITE_BUFFER_SIZE (64 * 1024 * 1024)
#define DEFAULT_MAX_HEADER_LIST_SIZE (16 * 1024) #define DEFAULT_MAX_HEADER_LIST_SIZE (16 * 1024)
#define DEFAULT_KEEPALIVE_TIME_SECOND INT_MAX #define DEFAULT_CLIENT_KEEPALIVE_TIME_S INT_MAX
#define DEFAULT_KEEPALIVE_TIMEOUT_SECOND 20 #define DEFAULT_CLIENT_KEEPALIVE_TIMEOUT_S 20
#define DEFAULT_KEEPALIVE_PERMIT_WITHOUT_CALLS false #define DEFAULT_KEEPALIVE_PERMIT_WITHOUT_CALLS false
static int g_default_client_keepalive_time_s = DEFAULT_CLIENT_KEEPALIVE_TIME_S;
static int g_default_client_keepalive_timeout_s =
DEFAULT_CLIENT_KEEPALIVE_TIMEOUT_S;
static bool g_default_keepalive_permit_without_calls =
DEFAULT_KEEPALIVE_PERMIT_WITHOUT_CALLS;
#define MAX_CLIENT_STREAM_ID 0x7fffffffu #define MAX_CLIENT_STREAM_ID 0x7fffffffu
int grpc_http_trace = 0; int grpc_http_trace = 0;
int grpc_flowctl_trace = 0; int grpc_flowctl_trace = 0;
@ -345,15 +351,16 @@ static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
/* client-side keepalive setting */ /* client-side keepalive setting */
t->keepalive_time = t->keepalive_time =
DEFAULT_KEEPALIVE_TIME_SECOND == INT_MAX g_default_client_keepalive_time_s == INT_MAX
? gpr_inf_future(GPR_TIMESPAN) ? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(DEFAULT_KEEPALIVE_TIME_SECOND, GPR_TIMESPAN); : gpr_time_from_seconds(g_default_client_keepalive_time_s,
GPR_TIMESPAN);
t->keepalive_timeout = t->keepalive_timeout =
DEFAULT_KEEPALIVE_TIMEOUT_SECOND == INT_MAX g_default_client_keepalive_timeout_s == INT_MAX
? gpr_inf_future(GPR_TIMESPAN) ? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(DEFAULT_KEEPALIVE_TIMEOUT_SECOND, : gpr_time_from_seconds(g_default_client_keepalive_timeout_s,
GPR_TIMESPAN); GPR_TIMESPAN);
t->keepalive_permit_without_calls = DEFAULT_KEEPALIVE_PERMIT_WITHOUT_CALLS; t->keepalive_permit_without_calls = g_default_keepalive_permit_without_calls;
if (channel_args) { if (channel_args) {
for (i = 0; i < channel_args->num_args; i++) { for (i = 0; i < channel_args->num_args; i++) {
@ -403,24 +410,25 @@ static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
t->enable_bdp_probe = grpc_channel_arg_get_integer( t->enable_bdp_probe = grpc_channel_arg_get_integer(
&channel_args->args[i], (grpc_integer_options){1, 0, 1}); &channel_args->args[i], (grpc_integer_options){1, 0, 1});
} else if (0 == strcmp(channel_args->args[i].key, } else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_HTTP2_KEEPALIVE_TIME)) { GRPC_ARG_CLIENT_KEEPALIVE_TIME_S)) {
const int value = grpc_channel_arg_get_integer( const int value = grpc_channel_arg_get_integer(
&channel_args->args[i], &channel_args->args[i],
(grpc_integer_options){DEFAULT_KEEPALIVE_TIME_SECOND, 1, INT_MAX}); (grpc_integer_options){g_default_client_keepalive_time_s, 1,
INT_MAX});
t->keepalive_time = value == INT_MAX t->keepalive_time = value == INT_MAX
? gpr_inf_future(GPR_TIMESPAN) ? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(value, GPR_TIMESPAN); : gpr_time_from_seconds(value, GPR_TIMESPAN);
} else if (0 == strcmp(channel_args->args[i].key, } else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_HTTP2_KEEPALIVE_TIMEOUT)) { GRPC_ARG_CLIENT_KEEPALIVE_TIMEOUT_S)) {
const int value = grpc_channel_arg_get_integer( const int value = grpc_channel_arg_get_integer(
&channel_args->args[i], &channel_args->args[i],
(grpc_integer_options){DEFAULT_KEEPALIVE_TIMEOUT_SECOND, 0, (grpc_integer_options){g_default_client_keepalive_timeout_s, 0,
INT_MAX}); INT_MAX});
t->keepalive_timeout = value == INT_MAX t->keepalive_timeout = value == INT_MAX
? gpr_inf_future(GPR_TIMESPAN) ? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(value, GPR_TIMESPAN); : gpr_time_from_seconds(value, GPR_TIMESPAN);
} else if (0 == strcmp(channel_args->args[i].key, } else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_HTTP2_KEEPALIVE_PERMIT_WITHOUT_CALLS)) { GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS)) {
t->keepalive_permit_without_calls = t->keepalive_permit_without_calls =
(uint32_t)grpc_channel_arg_get_integer( (uint32_t)grpc_channel_arg_get_integer(
&channel_args->args[i], (grpc_integer_options){0, 0, 1}); &channel_args->args[i], (grpc_integer_options){0, 0, 1});
@ -1210,8 +1218,13 @@ static void perform_stream_op_locked(grpc_exec_ctx *exec_ctx, void *stream_op,
} }
} else { } else {
GPR_ASSERT(s->id != 0); GPR_ASSERT(s->id != 0);
grpc_chttp2_become_writable(exec_ctx, t, s, grpc_chttp2_stream_write_type write_type =
GRPC_CHTTP2_STREAM_WRITE_INITIATE_COVERED, GRPC_CHTTP2_STREAM_WRITE_INITIATE_COVERED;
if (op->send_message != NULL &&
(op->send_message->flags & GRPC_WRITE_BUFFER_HINT)) {
write_type = GRPC_CHTTP2_STREAM_WRITE_PIGGYBACK;
}
grpc_chttp2_become_writable(exec_ctx, t, s, write_type,
"op.send_initial_metadata"); "op.send_initial_metadata");
} }
} else { } else {
@ -2103,6 +2116,32 @@ static void finish_bdp_ping_locked(grpc_exec_ctx *exec_ctx, void *tp,
GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "bdp_ping"); GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "bdp_ping");
} }
void grpc_chttp2_config_default_keepalive_args(grpc_channel_args *args) {
size_t i;
if (args) {
for (i = 0; i < args->num_args; i++) {
if (0 == strcmp(args->args[i].key, GRPC_ARG_CLIENT_KEEPALIVE_TIME_S)) {
g_default_client_keepalive_time_s = grpc_channel_arg_get_integer(
&args->args[i], (grpc_integer_options){
g_default_client_keepalive_time_s, 1, INT_MAX});
} else if (0 == strcmp(args->args[i].key,
GRPC_ARG_CLIENT_KEEPALIVE_TIMEOUT_S)) {
g_default_client_keepalive_timeout_s = grpc_channel_arg_get_integer(
&args->args[i],
(grpc_integer_options){g_default_client_keepalive_timeout_s, 0,
INT_MAX});
} else if (0 == strcmp(args->args[i].key,
GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS)) {
g_default_keepalive_permit_without_calls =
(uint32_t)grpc_channel_arg_get_integer(
&args->args[i],
(grpc_integer_options){g_default_keepalive_permit_without_calls,
0, 1});
}
}
}
}
static void init_keepalive_ping_locked(grpc_exec_ctx *exec_ctx, void *arg, static void init_keepalive_ping_locked(grpc_exec_ctx *exec_ctx, void *arg,
grpc_error *error) { grpc_error *error) {
grpc_chttp2_transport *t = arg; grpc_chttp2_transport *t = arg;

@ -40,6 +40,8 @@
#include <grpc/support/log.h> #include <grpc/support/log.h>
#include <grpc/support/string_util.h> #include <grpc/support/string_util.h>
static bool g_disable_ping_ack = false;
grpc_slice grpc_chttp2_ping_create(uint8_t ack, uint64_t opaque_8bytes) { grpc_slice grpc_chttp2_ping_create(uint8_t ack, uint64_t opaque_8bytes) {
grpc_slice slice = grpc_slice_malloc(9 + 8); grpc_slice slice = grpc_slice_malloc(9 + 8);
uint8_t *p = GRPC_SLICE_START_PTR(slice); uint8_t *p = GRPC_SLICE_START_PTR(slice);
@ -101,15 +103,21 @@ grpc_error *grpc_chttp2_ping_parser_parse(grpc_exec_ctx *exec_ctx, void *parser,
if (p->is_ack) { if (p->is_ack) {
grpc_chttp2_ack_ping(exec_ctx, t, p->opaque_8bytes); grpc_chttp2_ack_ping(exec_ctx, t, p->opaque_8bytes);
} else { } else {
if (t->ping_ack_count == t->ping_ack_capacity) { if (!g_disable_ping_ack) {
t->ping_ack_capacity = GPR_MAX(t->ping_ack_capacity * 3 / 2, 3); if (t->ping_ack_count == t->ping_ack_capacity) {
t->ping_acks = gpr_realloc( t->ping_ack_capacity = GPR_MAX(t->ping_ack_capacity * 3 / 2, 3);
t->ping_acks, t->ping_ack_capacity * sizeof(*t->ping_acks)); t->ping_acks = gpr_realloc(
t->ping_acks, t->ping_ack_capacity * sizeof(*t->ping_acks));
}
t->ping_acks[t->ping_ack_count++] = p->opaque_8bytes;
grpc_chttp2_initiate_write(exec_ctx, t, false, "ping response");
} }
t->ping_acks[t->ping_ack_count++] = p->opaque_8bytes;
grpc_chttp2_initiate_write(exec_ctx, t, false, "ping response");
} }
} }
return GRPC_ERROR_NONE; return GRPC_ERROR_NONE;
} }
void grpc_set_disable_ping_ack(bool disable_ping_ack) {
g_disable_ping_ack = disable_ping_ack;
}

@ -53,4 +53,7 @@ grpc_error *grpc_chttp2_ping_parser_parse(grpc_exec_ctx *exec_ctx, void *parser,
grpc_chttp2_stream *s, grpc_chttp2_stream *s,
grpc_slice slice, int is_last); grpc_slice slice, int is_last);
/* Test-only function for disabling ping ack */
void grpc_set_disable_ping_ack(bool disable_ping_ack);
#endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FRAME_PING_H */ #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_FRAME_PING_H */

@ -827,4 +827,8 @@ void grpc_chttp2_fail_pending_writes(grpc_exec_ctx *exec_ctx,
uint32_t grpc_chttp2_target_incoming_window(grpc_chttp2_transport *t); uint32_t grpc_chttp2_target_incoming_window(grpc_chttp2_transport *t);
/** Set the default keepalive configurations, must only be called at
initialization */
void grpc_chttp2_config_default_keepalive_args(grpc_channel_args *args);
#endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_INTERNAL_H */ #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_INTERNAL_H */

@ -67,7 +67,8 @@ ClientContext::ClientContext()
call_canceled_(false), call_canceled_(false),
deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)), deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)),
census_context_(nullptr), census_context_(nullptr),
propagate_from_call_(nullptr) { propagate_from_call_(nullptr),
initial_metadata_corked_(false) {
g_client_callbacks->DefaultConstructor(this); g_client_callbacks->DefaultConstructor(this);
} }

@ -36,6 +36,7 @@
#include <grpc/grpc.h> #include <grpc/grpc.h>
#include <grpc/support/time.h> #include <grpc/support/time.h>
#import <RxLibrary/GRXConcurrentWriteable.h> #import <RxLibrary/GRXConcurrentWriteable.h>
#import <RxLibrary/GRXImmediateSingleWriter.h>
#import "private/GRPCConnectivityMonitor.h" #import "private/GRPCConnectivityMonitor.h"
#import "private/GRPCHost.h" #import "private/GRPCHost.h"
@ -45,6 +46,11 @@
#import "private/NSDictionary+GRPC.h" #import "private/NSDictionary+GRPC.h"
#import "private/NSError+GRPC.h" #import "private/NSError+GRPC.h"
// At most 6 ops can be in an op batch for a client: SEND_INITIAL_METADATA,
// SEND_MESSAGE, SEND_CLOSE_FROM_CLIENT, RECV_INITIAL_METADATA, RECV_MESSAGE,
// and RECV_STATUS_ON_CLIENT.
NSInteger kMaxClientBatch = 6;
NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey"; NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey";
NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
static NSMutableDictionary *callFlags; static NSMutableDictionary *callFlags;
@ -100,6 +106,13 @@ static NSMutableDictionary *callFlags;
GRPCCall *_retainSelf; GRPCCall *_retainSelf;
GRPCRequestHeaders *_requestHeaders; GRPCRequestHeaders *_requestHeaders;
// In the case that the call is a unary call (i.e. the writer to GRPCCall is of type
// GRXImmediateSingleWriter), GRPCCall will delay sending ops (not send them to C core
// immediately) and buffer them into a batch _unaryOpBatch. The batch is sent to C core when
// the SendClose op is added.
BOOL _unaryCall;
NSMutableArray *_unaryOpBatch;
} }
@synthesize state = _state; @synthesize state = _state;
@ -157,6 +170,11 @@ static NSMutableDictionary *callFlags;
_requestWriter = requestWriter; _requestWriter = requestWriter;
_requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self]; _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
if ([requestWriter isKindOfClass:[GRXImmediateSingleWriter class]]) {
_unaryCall = YES;
_unaryOpBatch = [NSMutableArray arrayWithCapacity:kMaxClientBatch];
}
} }
return self; return self;
} }
@ -165,6 +183,9 @@ static NSMutableDictionary *callFlags;
- (void)finishWithError:(NSError *)errorOrNil { - (void)finishWithError:(NSError *)errorOrNil {
@synchronized(self) { @synchronized(self) {
if (_state == GRXWriterStateFinished) {
return;
}
_state = GRXWriterStateFinished; _state = GRXWriterStateFinished;
} }
@ -254,15 +275,22 @@ static NSMutableDictionary *callFlags;
- (void)sendHeaders:(NSDictionary *)headers { - (void)sendHeaders:(NSDictionary *)headers {
// TODO(jcanizales): Add error handlers for async failures // TODO(jcanizales): Add error handlers for async failures
[_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc] initWithMetadata:headers GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc] initWithMetadata:headers
flags:[GRPCCall callFlagsForHost:_host path:_path] flags:[GRPCCall callFlagsForHost:_host path:_path]
handler:nil]]]; handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA
if (!_unaryCall) {
[_wrappedCall startBatchWithOperations:@[op]];
} else {
[_unaryOpBatch addObject:op];
}
} }
#pragma mark GRXWriteable implementation #pragma mark GRXWriteable implementation
// Only called from the call queue. The error handler will be called from the // Only called from the call queue. The error handler will be called from the
// network queue if the write didn't succeed. // network queue if the write didn't succeed.
// If the call is a unary call, parameter \a errorHandler will be ignored and
// the error handler of GRPCOpSendClose will be executed in case of error.
- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler { - (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
__weak GRPCCall *weakSelf = self; __weak GRPCCall *weakSelf = self;
@ -275,9 +303,17 @@ static NSMutableDictionary *callFlags;
} }
} }
}; };
[_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message
handler:resumingHandler]] GRPCOpSendMessage *op = [[GRPCOpSendMessage alloc] initWithMessage:message
errorHandler:errorHandler]; handler:resumingHandler];
if (!_unaryCall) {
[_wrappedCall startBatchWithOperations:@[op]
errorHandler:errorHandler];
} else {
// Ignored errorHandler since it is the same as the one for GRPCOpSendClose.
// TODO (mxyan): unify the error handlers of all Ops into a single closure.
[_unaryOpBatch addObject:op];
}
} }
- (void)writeValue:(id)value { - (void)writeValue:(id)value {
@ -302,8 +338,14 @@ static NSMutableDictionary *callFlags;
// Only called from the call queue. The error handler will be called from the // Only called from the call queue. The error handler will be called from the
// network queue if the requests stream couldn't be closed successfully. // network queue if the requests stream couldn't be closed successfully.
- (void)finishRequestWithErrorHandler:(void (^)())errorHandler { - (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
[_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]] if (!_unaryCall) {
errorHandler:errorHandler]; [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
errorHandler:errorHandler];
} else {
[_unaryOpBatch addObject:[[GRPCOpSendClose alloc] init]];
[_wrappedCall startBatchWithOperations:_unaryOpBatch
errorHandler:errorHandler];
}
} }
- (void)writesFinishedWithError:(NSError *)errorOrNil { - (void)writesFinishedWithError:(NSError *)errorOrNil {

@ -0,0 +1,61 @@
/*
*
* Copyright 2017, 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.
*
*/
#ifdef GRPC_TEST_OBJC
#import "../GRPCCall.h"
/**
* Methods used for gRPC internal tests. DO NOT USE.
*/
@interface GRPCCall (InternalTests)
/**
* Enables logging of op batches. Memory consumption increases as more ops are logged.
*
* This function is for internal testing of gRPC only. It is not part of gRPC's public interface.
* Do not use in production. To enable, set the preprocessor flag GRPC_TEST_OBJC.
*/
+ (void)enableOpBatchLog:(BOOL)enabled;
/**
* Obtain the logged op batches. Invoking this method will clean the log.
*
* This function is for internal testing of gRPC only. It is not part of gRPC's public interface.
* Do not use in production. To enable, set the preprocessor flag GRPC_TEST_OBJC.
*/
+ (NSArray *)obtainAndCleanOpBatchLog;
@end
#endif

@ -0,0 +1,52 @@
/*
*
* Copyright 2017, 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.
*
*/
#ifdef GRPC_TEST_OBJC
#import "GRPCCall+InternalTests.h"
#import "../private/GRPCOpBatchLog.h"
@implementation GRPCCall (InternalTests)
+ (void)enableOpBatchLog:(BOOL)enabled {
[GRPCOpBatchLog enableOpBatchLog:enabled];
}
+ (NSArray *)obtainAndCleanOpBatchLog {
return [GRPCOpBatchLog obtainAndCleanOpBatchLog];
}
@end
#endif

@ -0,0 +1,59 @@
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifdef GRPC_TEST_OBJC
/**
* Logs the op batches of a client. Used for testing.
*/
@interface GRPCOpBatchLog : NSObject
/**
* Enables logging of op batches. Memory consumption increases as more ops are logged.
*/
+ (void)enableOpBatchLog:(BOOL)enabled;
/**
* Add an op batch to log.
*/
+ (void)addOpBatchToLog:(NSArray *)batch;
/**
* Obtain the logged op batches. Invoking this method will clean the log.
*/
+ (NSArray *)obtainAndCleanOpBatchLog;
@end
#endif

@ -0,0 +1,72 @@
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifdef GRPC_TEST_OBJC
#import "GRPCOpBatchLog.h"
static NSMutableArray *opBatchLog = nil;
@implementation GRPCOpBatchLog
+ (void)enableOpBatchLog:(BOOL)enabled {
@synchronized (opBatchLog) {
if (enabled) {
if (!opBatchLog) {
opBatchLog = [NSMutableArray array];
}
} else {
if (opBatchLog) {
opBatchLog = nil;
}
}
}
}
+ (void)addOpBatchToLog:(NSArray *)batch {
@synchronized (opBatchLog) {
[opBatchLog addObject:batch];
}
}
+ (NSArray *)obtainAndCleanOpBatchLog {
@synchronized (opBatchLog) {
NSArray *out = opBatchLog;
opBatchLog = [NSMutableArray array];
return out;
}
}
@end
#endif

@ -44,6 +44,8 @@
#import "NSData+GRPC.h" #import "NSData+GRPC.h"
#import "NSError+GRPC.h" #import "NSError+GRPC.h"
#import "GRPCOpBatchLog.h"
@implementation GRPCOperation { @implementation GRPCOperation {
@protected @protected
// Most operation subclasses don't set any flags in the grpc_op, and rely on the flag member being // Most operation subclasses don't set any flags in the grpc_op, and rely on the flag member being
@ -274,6 +276,12 @@
} }
- (void)startBatchWithOperations:(NSArray *)operations errorHandler:(void (^)())errorHandler { - (void)startBatchWithOperations:(NSArray *)operations errorHandler:(void (^)())errorHandler {
// Keep logs of op batches when we are running tests. Disabled when in production for improved
// performance.
#ifdef GRPC_TEST_OBJC
[GRPCOpBatchLog addOpBatchToLog:operations];
#endif
size_t nops = operations.count; size_t nops = operations.count;
grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op)); grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op));
size_t i = 0; size_t i = 0;

@ -0,0 +1,50 @@
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#import <Foundation/Foundation.h>
#import "GRXImmediateWriter.h"
/**
* Utility to construct GRXWriter instances from values that are immediately available when
* required.
*/
@interface GRXImmediateSingleWriter : GRXImmediateWriter
/**
* Returns a writer that sends the passed value to its writeable and then finishes (releasing the
* value).
*/
+ (GRXWriter *)writerWithValue:(id)value;
@end

@ -0,0 +1,89 @@
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#import "GRXImmediateSingleWriter.h"
@implementation GRXImmediateSingleWriter {
id _value;
id<GRXWriteable> _writeable;
}
@synthesize state = _state;
- (instancetype)initWithValue:(id)value {
if (self = [super init]) {
_value = value;
_state = GRXWriterStateNotStarted;
}
return self;
}
+ (GRXWriter *)writerWithValue:(id)value {
return [[self alloc] initWithValue:value];
}
- (void)startWithWriteable:(id<GRXWriteable>)writeable {
_state = GRXWriterStateStarted;
_writeable = writeable;
[writeable writeValue:_value];
[self finish];
}
- (void)finish {
_state = GRXWriterStateFinished;
_value = nil;
id<GRXWriteable> writeable = _writeable;
_writeable = nil;
[writeable writesFinishedWithError:nil];
}
// Overwrite the setter to disallow manual state transition. The getter
// of _state is synthesized.
- (void)setState:(GRXWriterState)newState {
// Manual state transition is not allowed
return;
}
// Overrides [requestWriter(Transformations):map:] for Protocol Buffers
// encoding.
// We need the return value of this map to be a GRXImmediateSingleWriter but
// the original \a map function returns a new Writer of another type. So we
// need to override this function here.
- (GRXWriter *)map:(id (^)(id))map {
// Since _value is available when creating the object, we can simply
// apply the map and store the output.
_value = map(_value);
return self;
}
@end

@ -34,6 +34,7 @@
#import "GRXWriter+Immediate.h" #import "GRXWriter+Immediate.h"
#import "GRXImmediateWriter.h" #import "GRXImmediateWriter.h"
#import "GRXImmediateSingleWriter.h"
@implementation GRXWriter (Immediate) @implementation GRXWriter (Immediate)
@ -50,7 +51,7 @@
} }
+ (instancetype)writerWithValue:(id)value { + (instancetype)writerWithValue:(id)value {
return [GRXImmediateWriter writerWithValue:value]; return [GRXImmediateSingleWriter writerWithValue:value];
} }
+ (instancetype)writerWithError:(NSError *)error { + (instancetype)writerWithError:(NSError *)error {

@ -38,6 +38,7 @@
#import <GRPCClient/GRPCCall+ChannelArg.h> #import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+OAuth2.h> #import <GRPCClient/GRPCCall+OAuth2.h>
#import <GRPCClient/GRPCCall+Tests.h> #import <GRPCClient/GRPCCall+Tests.h>
#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
#import <ProtoRPC/ProtoMethod.h> #import <ProtoRPC/ProtoMethod.h>
#import <RemoteTest/Messages.pbobjc.h> #import <RemoteTest/Messages.pbobjc.h>
#import <RxLibrary/GRXWriteable.h> #import <RxLibrary/GRXWriteable.h>

@ -38,6 +38,7 @@
#import <Cronet/Cronet.h> #import <Cronet/Cronet.h>
#import <GRPCClient/GRPCCall+ChannelArg.h> #import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+Tests.h> #import <GRPCClient/GRPCCall+Tests.h>
#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
#import <GRPCClient/GRPCCall+Cronet.h> #import <GRPCClient/GRPCCall+Cronet.h>
#import <ProtoRPC/ProtoRPC.h> #import <ProtoRPC/ProtoRPC.h>
#import <RemoteTest/Messages.pbobjc.h> #import <RemoteTest/Messages.pbobjc.h>
@ -45,6 +46,8 @@
#import <RemoteTest/Test.pbrpc.h> #import <RemoteTest/Test.pbrpc.h>
#import <RxLibrary/GRXBufferedPipe.h> #import <RxLibrary/GRXBufferedPipe.h>
#import <RxLibrary/GRXWriter+Immediate.h> #import <RxLibrary/GRXWriter+Immediate.h>
#import <grpc/support/log.h>
#import <grpc/grpc.h>
#define TEST_TIMEOUT 32 #define TEST_TIMEOUT 32
@ -97,15 +100,6 @@
return 0; return 0;
} }
+ (void)setUp {
#ifdef GRPC_COMPILE_WITH_CRONET
// Cronet setup
[Cronet setHttp2Enabled:YES];
[Cronet start];
[GRPCCall useCronetWithEngine:[Cronet getGlobalEngine]];
#endif
}
- (void)setUp { - (void)setUp {
self.continueAfterFailure = NO; self.continueAfterFailure = NO;
@ -155,6 +149,44 @@
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
} }
- (void)testPacketCoalescing {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.responseType = RMTPayloadType_Compressable;
request.responseSize = 10;
request.payload.body = [NSMutableData dataWithLength:10];
[GRPCCall enableOpBatchLog:YES];
[_service unaryCallWithRequest:request handler:^(RMTSimpleResponse *response, NSError *error) {
XCTAssertNil(error, @"Finished with unexpected error: %@", error);
RMTSimpleResponse *expectedResponse = [RMTSimpleResponse message];
expectedResponse.payload.type = RMTPayloadType_Compressable;
expectedResponse.payload.body = [NSMutableData dataWithLength:10];
XCTAssertEqualObjects(response, expectedResponse);
// The test is a success if there is a batch of exactly 3 ops (SEND_INITIAL_METADATA,
// SEND_MESSAGE, SEND_CLOSE_FROM_CLIENT). Without packet coalescing each batch of ops contains
// only one op.
NSArray *opBatches = [GRPCCall obtainAndCleanOpBatchLog];
const NSInteger kExpectedOpBatchSize = 3;
for (NSObject *o in opBatches) {
if ([o isKindOfClass:[NSArray class]]) {
NSArray *batch = (NSArray *)o;
if ([batch count] == kExpectedOpBatchSize) {
[expectation fulfill];
break;
}
}
}
}];
[self waitForExpectationsWithTimeout:16 handler:nil];
[GRPCCall enableOpBatchLog:NO];
}
- (void)test4MBResponsesAreAccepted { - (void)test4MBResponsesAreAccepted {
XCTAssertNotNil(self.class.host); XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"MaxResponseSize"]; __weak XCTestExpectation *expectation = [self expectationWithDescription:@"MaxResponseSize"];

@ -32,6 +32,7 @@
*/ */
#import <GRPCClient/GRPCCall+Tests.h> #import <GRPCClient/GRPCCall+Tests.h>
#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
#import "InteropTests.h" #import "InteropTests.h"

@ -32,6 +32,7 @@
*/ */
#import <GRPCClient/GRPCCall+Tests.h> #import <GRPCClient/GRPCCall+Tests.h>
#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
#import "InteropTests.h" #import "InteropTests.h"

@ -32,6 +32,7 @@
*/ */
#import <GRPCClient/GRPCCall+Tests.h> #import <GRPCClient/GRPCCall+Tests.h>
#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
#import "InteropTests.h" #import "InteropTests.h"

@ -32,6 +32,10 @@
*/ */
#import <GRPCClient/GRPCCall+Tests.h> #import <GRPCClient/GRPCCall+Tests.h>
#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
#import <Cronet/Cronet.h>
#import <GRPCClient/GRPCCall+Cronet.h>
#import "InteropTests.h" #import "InteropTests.h"

@ -97,15 +97,20 @@ post_install do |installer|
# GPR_UNREACHABLE_CODE causes "Control may reach end of non-void # GPR_UNREACHABLE_CODE causes "Control may reach end of non-void
# function" warning # function" warning
config.build_settings['GCC_WARN_ABOUT_RETURN_TYPE'] = 'NO' config.build_settings['GCC_WARN_ABOUT_RETURN_TYPE'] = 'NO'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_CRONET_WITH_PACKET_COALESCING=1'
end end
end end
# Activate Cronet for the dedicated build configuration 'Cronet', which will be used solely by # Activate Cronet for the dedicated build configuration 'Cronet', which will be used solely by
# the test target 'InteropTestsRemoteWithCronet' # the test target 'InteropTestsRemoteWithCronet'
# Activate GRPCCall+InternalTests functions for the dedicated build configuration 'Test', which will
# be used by all test targets using it.
if target.name == 'gRPC' if target.name == 'gRPC'
target.build_configurations.each do |config| target.build_configurations.each do |config|
if config.name == 'Cronet' if config.name == 'Cronet'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_COMPILE_WITH_CRONET=1' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_COMPILE_WITH_CRONET=1 GRPC_TEST_OBJC=1'
elsif config.name == 'Test'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_TEST_OBJC=1'
end end
end end
end end

@ -125,8 +125,10 @@
0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; }; 0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.debug.xcconfig"; sourceTree = "<group>"; }; 0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.debug.xcconfig"; sourceTree = "<group>"; };
14B09A58FEE53A7A6B838920 /* Pods-InteropTestsLocalSSL.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.cronet.xcconfig"; sourceTree = "<group>"; }; 14B09A58FEE53A7A6B838920 /* Pods-InteropTestsLocalSSL.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.cronet.xcconfig"; sourceTree = "<group>"; };
1588C85DEAF7FC0ACDEA4C02 /* Pods-InteropTestsLocalCleartext.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.test.xcconfig"; sourceTree = "<group>"; };
17F60BF2871F6AF85FB3FA12 /* Pods-InteropTestsRemoteWithCronet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; sourceTree = "<group>"; }; 17F60BF2871F6AF85FB3FA12 /* Pods-InteropTestsRemoteWithCronet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.debug.xcconfig"; sourceTree = "<group>"; };
20DFF2F3C97EF098FE5A3171 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 20DFF2F3C97EF098FE5A3171 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
2B89F3037963E6EDDD48D8C3 /* Pods-InteropTestsRemoteWithCronet.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.test.xcconfig"; sourceTree = "<group>"; };
35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
386712AEACF7C2190C4B8B3F /* Pods-CronetUnitTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.cronet.xcconfig"; sourceTree = "<group>"; }; 386712AEACF7C2190C4B8B3F /* Pods-CronetUnitTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.cronet.xcconfig"; sourceTree = "<group>"; };
3B0861FC805389C52DB260D4 /* Pods-RxLibraryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.release.xcconfig"; sourceTree = "<group>"; }; 3B0861FC805389C52DB260D4 /* Pods-RxLibraryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.release.xcconfig"; sourceTree = "<group>"; };
@ -162,15 +164,22 @@
63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalSSL.m; sourceTree = "<group>"; }; 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalSSL.m; sourceTree = "<group>"; };
63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; }; 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; };
64F68A9A6A63CC930DD30A6E /* Pods-CronetUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.debug.xcconfig"; sourceTree = "<group>"; }; 64F68A9A6A63CC930DD30A6E /* Pods-CronetUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.debug.xcconfig"; sourceTree = "<group>"; };
6793C9D019CB268C5BB491A2 /* Pods-CoreCronetEnd2EndTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.test.xcconfig"; sourceTree = "<group>"; };
781089FAE980F51F88A3BE0B /* Pods-RxLibraryUnitTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.test.xcconfig"; sourceTree = "<group>"; };
79C68EFFCB5533475D810B79 /* Pods-RxLibraryUnitTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.cronet.xcconfig"; sourceTree = "<group>"; }; 79C68EFFCB5533475D810B79 /* Pods-RxLibraryUnitTests.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.cronet.xcconfig"; sourceTree = "<group>"; };
7A2E97E3F469CC2A758D77DE /* Pods-InteropTestsLocalSSL.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.release.xcconfig"; sourceTree = "<group>"; }; 7A2E97E3F469CC2A758D77DE /* Pods-InteropTestsLocalSSL.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.release.xcconfig"; sourceTree = "<group>"; };
9E9444C764F0FFF64A7EB58E /* libPods-InteropTestsRemoteWithCronet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsRemoteWithCronet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 9E9444C764F0FFF64A7EB58E /* libPods-InteropTestsRemoteWithCronet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsRemoteWithCronet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A0361771A855917162911180 /* Pods-Tests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.test.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.test.xcconfig"; sourceTree = "<group>"; };
A58BE6DF1C62D1739EBB2C78 /* libPods-RxLibraryUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RxLibraryUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A58BE6DF1C62D1739EBB2C78 /* libPods-RxLibraryUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RxLibraryUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A6F832FCEFA6F6881E620F12 /* Pods-InteropTestsRemote.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.test.xcconfig"; sourceTree = "<group>"; };
AA7CB64B4DD9915AE7C03163 /* Pods-InteropTestsLocalCleartext.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.cronet.xcconfig"; sourceTree = "<group>"; }; AA7CB64B4DD9915AE7C03163 /* Pods-InteropTestsLocalCleartext.cronet.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.cronet.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.cronet.xcconfig"; sourceTree = "<group>"; };
AC414EF7A6BF76ED02B6E480 /* Pods-InteropTestsRemoteWithCronet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.release.xcconfig"; sourceTree = "<group>"; }; AC414EF7A6BF76ED02B6E480 /* Pods-InteropTestsRemoteWithCronet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemoteWithCronet.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemoteWithCronet/Pods-InteropTestsRemoteWithCronet.release.xcconfig"; sourceTree = "<group>"; };
B226619DC4E709E0FFFF94B8 /* Pods-CronetUnitTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CronetUnitTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-CronetUnitTests/Pods-CronetUnitTests.test.xcconfig"; sourceTree = "<group>"; };
B94C27C06733CF98CE1B2757 /* Pods-AllTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.debug.xcconfig"; sourceTree = "<group>"; }; B94C27C06733CF98CE1B2757 /* Pods-AllTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.debug.xcconfig"; sourceTree = "<group>"; };
C6134277D2EB8B380862A03F /* libPods-CronetUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CronetUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C6134277D2EB8B380862A03F /* libPods-CronetUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CronetUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
CAE086D5B470DA367D415AB0 /* libPods-AllTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AllTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; CAE086D5B470DA367D415AB0 /* libPods-AllTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AllTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
D13BEC8181B8E678A1B52F54 /* Pods-InteropTestsLocalSSL.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.test.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.test.xcconfig"; sourceTree = "<group>"; };
DB1F4391AF69D20D38D74B67 /* Pods-AllTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.test.xcconfig"; sourceTree = "<group>"; };
DBE059B4AC7A51919467EEC0 /* libPods-InteropTestsRemote.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsRemote.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DBE059B4AC7A51919467EEC0 /* libPods-InteropTestsRemote.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsRemote.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DBEDE45BDA60DF1E1C8950C0 /* libPods-InteropTestsLocalSSL.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsLocalSSL.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DBEDE45BDA60DF1E1C8950C0 /* libPods-InteropTestsLocalSSL.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsLocalSSL.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DC3CA1D948F068E76957A861 /* Pods-InteropTestsRemote.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.debug.xcconfig"; sourceTree = "<group>"; }; DC3CA1D948F068E76957A861 /* Pods-InteropTestsRemote.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.debug.xcconfig"; sourceTree = "<group>"; };
@ -317,6 +326,15 @@
64F68A9A6A63CC930DD30A6E /* Pods-CronetUnitTests.debug.xcconfig */, 64F68A9A6A63CC930DD30A6E /* Pods-CronetUnitTests.debug.xcconfig */,
386712AEACF7C2190C4B8B3F /* Pods-CronetUnitTests.cronet.xcconfig */, 386712AEACF7C2190C4B8B3F /* Pods-CronetUnitTests.cronet.xcconfig */,
02192CF1FF9534E3D18C65FC /* Pods-CronetUnitTests.release.xcconfig */, 02192CF1FF9534E3D18C65FC /* Pods-CronetUnitTests.release.xcconfig */,
DB1F4391AF69D20D38D74B67 /* Pods-AllTests.test.xcconfig */,
6793C9D019CB268C5BB491A2 /* Pods-CoreCronetEnd2EndTests.test.xcconfig */,
B226619DC4E709E0FFFF94B8 /* Pods-CronetUnitTests.test.xcconfig */,
1588C85DEAF7FC0ACDEA4C02 /* Pods-InteropTestsLocalCleartext.test.xcconfig */,
D13BEC8181B8E678A1B52F54 /* Pods-InteropTestsLocalSSL.test.xcconfig */,
A6F832FCEFA6F6881E620F12 /* Pods-InteropTestsRemote.test.xcconfig */,
2B89F3037963E6EDDD48D8C3 /* Pods-InteropTestsRemoteWithCronet.test.xcconfig */,
781089FAE980F51F88A3BE0B /* Pods-RxLibraryUnitTests.test.xcconfig */,
A0361771A855917162911180 /* Pods-Tests.test.xcconfig */,
); );
name = Pods; name = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1237,6 +1255,210 @@
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
5E1228981E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Test;
};
5E1228991E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A0361771A855917162911180 /* Pods-Tests.test.xcconfig */;
buildSettings = {
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Test;
};
5E12289A1E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = DB1F4391AF69D20D38D74B67 /* Pods-AllTests.test.xcconfig */;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
"GRPC_TEST_OBJC=1",
);
INFOPLIST_FILE = Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
5E12289B1E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 781089FAE980F51F88A3BE0B /* Pods-RxLibraryUnitTests.test.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.RxLibraryUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
5E12289C1E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A6F832FCEFA6F6881E620F12 /* Pods-InteropTestsRemote.test.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"COCOAPODS=1",
"$(inherited)",
"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
"GRPC_TEST_OBJC=1",
);
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
5E12289D1E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D13BEC8181B8E678A1B52F54 /* Pods-InteropTestsLocalSSL.test.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"COCOAPODS=1",
"$(inherited)",
"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
"GRPC_TEST_OBJC=1",
);
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsLocalSSL;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
5E12289E1E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1588C85DEAF7FC0ACDEA4C02 /* Pods-InteropTestsLocalCleartext.test.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"COCOAPODS=1",
"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
"GRPC_TEST_OBJC=1",
);
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsLocalCleartext;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
5E12289F1E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6793C9D019CB268C5BB491A2 /* Pods-CoreCronetEnd2EndTests.test.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = CoreCronetEnd2EndTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.CoreCronetEnd2EndTests;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = "$(inherited) \"${PODS_ROOT}/../../../..\"";
};
name = Test;
};
5E1228A01E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2B89F3037963E6EDDD48D8C3 /* Pods-InteropTestsRemoteWithCronet.test.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"COCOAPODS=1",
"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
"GRPC_TEST_OBJC=1",
);
INFOPLIST_FILE = InteropTestsRemoteWithCronet/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsRemoteWithCronet;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
5E1228A11E4D400F00E8504F /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B226619DC4E709E0FFFF94B8 /* Pods-CronetUnitTests.test.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = CronetUnitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.CronetUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = "\"${PODS_ROOT}/../../../..\" $(inherited)";
};
name = Test;
};
5E8A5DAC1D3840B4000F8BC4 /* Debug */ = { 5E8A5DAC1D3840B4000F8BC4 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */; baseConfigurationReference = 0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */;
@ -1407,6 +1629,12 @@
buildSettings = { buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"COCOAPODS=1",
"$(inherited)",
"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
);
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@ -1471,10 +1699,10 @@
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)", "$(inherited)",
"COCOAPODS=1", "COCOAPODS=1",
"$(inherited)",
"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
"GRPC_COMPILE_WITH_CRONET=1", "GRPC_COMPILE_WITH_CRONET=1",
"GRPC_CRONET_WITH_PACKET_COALESCING=1", "GRPC_CRONET_WITH_PACKET_COALESCING=1",
"GRPC_TEST_OBJC=1",
); );
INFOPLIST_FILE = InteropTestsRemoteWithCronet/Info.plist; INFOPLIST_FILE = InteropTestsRemoteWithCronet/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3; IPHONEOS_DEPLOYMENT_TARGET = 9.3;
@ -1495,7 +1723,6 @@
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)", "$(inherited)",
"COCOAPODS=1", "COCOAPODS=1",
"$(inherited)",
"GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
); );
INFOPLIST_FILE = InteropTestsRemoteWithCronet/Info.plist; INFOPLIST_FILE = InteropTestsRemoteWithCronet/Info.plist;
@ -1770,6 +1997,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
5E8A5DAC1D3840B4000F8BC4 /* Debug */, 5E8A5DAC1D3840B4000F8BC4 /* Debug */,
5E12289F1E4D400F00E8504F /* Test */,
5EC3C7A71D4FC18C000330E2 /* Cronet */, 5EC3C7A71D4FC18C000330E2 /* Cronet */,
5E8A5DAD1D3840B4000F8BC4 /* Release */, 5E8A5DAD1D3840B4000F8BC4 /* Release */,
); );
@ -1780,6 +2008,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
5EAD6D2C1E27047400002378 /* Debug */, 5EAD6D2C1E27047400002378 /* Debug */,
5E1228A11E4D400F00E8504F /* Test */,
5EAD6D2D1E27047400002378 /* Cronet */, 5EAD6D2D1E27047400002378 /* Cronet */,
5EAD6D2E1E27047400002378 /* Release */, 5EAD6D2E1E27047400002378 /* Release */,
); );
@ -1790,6 +2019,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
5EE84BF91D4717E40050C6CC /* Debug */, 5EE84BF91D4717E40050C6CC /* Debug */,
5E1228A01E4D400F00E8504F /* Test */,
5EC3C7A81D4FC18C000330E2 /* Cronet */, 5EC3C7A81D4FC18C000330E2 /* Cronet */,
5EE84BFA1D4717E40050C6CC /* Release */, 5EE84BFA1D4717E40050C6CC /* Release */,
); );
@ -1800,6 +2030,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
63423F4E1B150A5F006CF63C /* Debug */, 63423F4E1B150A5F006CF63C /* Debug */,
5E12289A1E4D400F00E8504F /* Test */,
5EC3C7A21D4FC18C000330E2 /* Cronet */, 5EC3C7A21D4FC18C000330E2 /* Cronet */,
63423F4F1B150A5F006CF63C /* Release */, 63423F4F1B150A5F006CF63C /* Release */,
); );
@ -1810,6 +2041,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
635697D91B14FC11007A7283 /* Debug */, 635697D91B14FC11007A7283 /* Debug */,
5E1228981E4D400F00E8504F /* Test */,
5EC3C7A01D4FC18C000330E2 /* Cronet */, 5EC3C7A01D4FC18C000330E2 /* Cronet */,
635697DA1B14FC11007A7283 /* Release */, 635697DA1B14FC11007A7283 /* Release */,
); );
@ -1820,6 +2052,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
635697DC1B14FC11007A7283 /* Debug */, 635697DC1B14FC11007A7283 /* Debug */,
5E1228991E4D400F00E8504F /* Test */,
5EC3C7A11D4FC18C000330E2 /* Cronet */, 5EC3C7A11D4FC18C000330E2 /* Cronet */,
635697DD1B14FC11007A7283 /* Release */, 635697DD1B14FC11007A7283 /* Release */,
); );
@ -1830,6 +2063,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
63DC841C1BE15179000708E8 /* Debug */, 63DC841C1BE15179000708E8 /* Debug */,
5E12289B1E4D400F00E8504F /* Test */,
5EC3C7A31D4FC18C000330E2 /* Cronet */, 5EC3C7A31D4FC18C000330E2 /* Cronet */,
63DC841D1BE15179000708E8 /* Release */, 63DC841D1BE15179000708E8 /* Release */,
); );
@ -1840,6 +2074,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
63DC842C1BE15267000708E8 /* Debug */, 63DC842C1BE15267000708E8 /* Debug */,
5E12289C1E4D400F00E8504F /* Test */,
5EC3C7A41D4FC18C000330E2 /* Cronet */, 5EC3C7A41D4FC18C000330E2 /* Cronet */,
63DC842D1BE15267000708E8 /* Release */, 63DC842D1BE15267000708E8 /* Release */,
); );
@ -1850,6 +2085,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
63DC843D1BE15294000708E8 /* Debug */, 63DC843D1BE15294000708E8 /* Debug */,
5E12289D1E4D400F00E8504F /* Test */,
5EC3C7A51D4FC18C000330E2 /* Cronet */, 5EC3C7A51D4FC18C000330E2 /* Cronet */,
63DC843E1BE15294000708E8 /* Release */, 63DC843E1BE15294000708E8 /* Release */,
); );
@ -1860,6 +2096,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
63DC844C1BE152B5000708E8 /* Debug */, 63DC844C1BE152B5000708E8 /* Debug */,
5E12289E1E4D400F00E8504F /* Test */,
5EC3C7A61D4FC18C000330E2 /* Cronet */, 5EC3C7A61D4FC18C000330E2 /* Cronet */,
63DC844D1BE152B5000708E8 /* Release */, 63DC844D1BE152B5000708E8 /* Release */,
); );

@ -23,7 +23,7 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
@ -49,26 +49,6 @@
</Test> </Test>
</SkippedTests> </SkippedTests>
</TestableReference> </TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5E8A5DA31D3840B4000F8BC4"
BuildableName = "CoreCronetEnd2EndTests.xctest"
BlueprintName = "CoreCronetEnd2EndTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5EAD6D231E27047400002378"
BuildableName = "CronetUnitTests.xctest"
BlueprintName = "CronetUnitTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables> </Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
@ -83,7 +63,7 @@
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -121,7 +101,7 @@
</MacroExpansion> </MacroExpansion>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Test">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "Release" buildConfiguration = "Release"

@ -23,7 +23,7 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
@ -54,7 +54,7 @@
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -92,7 +92,7 @@
</MacroExpansion> </MacroExpansion>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Test">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "Release" buildConfiguration = "Release"

@ -23,7 +23,7 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
@ -57,7 +57,7 @@
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -86,7 +86,7 @@
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES">
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Test">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "Release" buildConfiguration = "Release"

@ -23,7 +23,7 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
@ -57,7 +57,7 @@
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -86,7 +86,7 @@
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES">
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Test">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "Release" buildConfiguration = "Release"

@ -57,6 +57,4 @@ xcodebuild \
build \ build \
-workspace *.xcworkspace \ -workspace *.xcworkspace \
-scheme $SCHEME \ -scheme $SCHEME \
-destination name="iPhone 6" \ -destination name="iPhone 6" | xcpretty
| egrep "$XCODEBUILD_FILTER" \
| egrep -v "(GPBDictionary|GPBArray)" -

@ -58,14 +58,22 @@ xcodebuild \
-workspace Tests.xcworkspace \ -workspace Tests.xcworkspace \
-scheme AllTests \ -scheme AllTests \
-destination name="iPhone 6" \ -destination name="iPhone 6" \
test \ test | xcpretty
| egrep "$XCODEBUILD_FILTER" \
| egrep -v "(GPBDictionary|GPBArray)" - xcodebuild \
-workspace Tests.xcworkspace \
-scheme CoreCronetEnd2EndTests \
-destination name="iPhone 6" \
test | xcpretty
xcodebuild \
-workspace Tests.xcworkspace \
-scheme CronetUnitTests \
-destination name="iPhone 6" \
test | xcpretty
xcodebuild \ xcodebuild \
-workspace Tests.xcworkspace \ -workspace Tests.xcworkspace \
-scheme InteropTestsRemoteWithCronet \ -scheme InteropTestsRemoteWithCronet \
-destination name="iPhone 6" \ -destination name="iPhone 6" \
test \ test | xcpretty
| egrep "$XCODEBUILD_FILTER" \
| egrep -v "(GPBDictionary|GPBArray)" -

@ -41,6 +41,7 @@
#include <grpc/support/log.h> #include <grpc/support/log.h>
#include <grpc/support/time.h> #include <grpc/support/time.h>
#include <grpc/support/useful.h> #include <grpc/support/useful.h>
#include "src/core/ext/transport/chttp2/transport/frame_ping.h"
#include "src/core/lib/channel/channel_args.h" #include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/iomgr/exec_ctx.h" #include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/support/env.h" #include "src/core/lib/support/env.h"
@ -109,13 +110,15 @@ static void test_keepalive_timeout(grpc_end2end_test_config config) {
grpc_raw_byte_buffer_create(&response_payload_slice, 1); grpc_raw_byte_buffer_create(&response_payload_slice, 1);
gpr_timespec deadline = five_seconds_time(); gpr_timespec deadline = five_seconds_time();
grpc_arg keepalive_args[2]; grpc_arg keepalive_args[] = {{.type = GRPC_ARG_INTEGER,
keepalive_args[0].type = GRPC_ARG_INTEGER; .key = GRPC_ARG_CLIENT_KEEPALIVE_TIME_S,
keepalive_args[0].key = GRPC_ARG_HTTP2_KEEPALIVE_TIME; .value.integer = 2},
keepalive_args[0].value.integer = 2; {.type = GRPC_ARG_INTEGER,
keepalive_args[1].type = GRPC_ARG_INTEGER; .key = GRPC_ARG_CLIENT_KEEPALIVE_TIMEOUT_S,
keepalive_args[1].key = GRPC_ARG_HTTP2_KEEPALIVE_TIMEOUT; .value.integer = 0},
keepalive_args[1].value.integer = 0; {.type = GRPC_ARG_INTEGER,
.key = GRPC_ARG_HTTP2_BDP_PROBE,
.value.integer = 1}};
grpc_channel_args *client_args = NULL; grpc_channel_args *client_args = NULL;
client_args = grpc_channel_args_copy_and_add(client_args, keepalive_args, 2); client_args = grpc_channel_args_copy_and_add(client_args, keepalive_args, 2);
@ -134,6 +137,9 @@ static void test_keepalive_timeout(grpc_end2end_test_config config) {
grpc_call_error error; grpc_call_error error;
grpc_slice details; grpc_slice details;
/* Disable ping ack to trigger the keepalive timeout */
grpc_set_disable_ping_ack(true);
c = grpc_channel_create_call( c = grpc_channel_create_call(
f.client, NULL, GRPC_PROPAGATE_DEFAULTS, f.cq, f.client, NULL, GRPC_PROPAGATE_DEFAULTS, f.cq,
grpc_slice_from_static_string("/foo"), grpc_slice_from_static_string("/foo"),

@ -139,23 +139,25 @@ static void increment(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
* polling_island_merge()[ev_epoll_linux.c], where the parent relationship was * polling_island_merge()[ev_epoll_linux.c], where the parent relationship was
* inverted. * inverted.
*/ */
#define NUM_FDS 2
#define NUM_POLLSETS 2
#define NUM_CLOSURES 4
static void test_pollset_queue_merge_items() { static void test_pollset_queue_merge_items() {
grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
const int num_fds = 2; test_fd tfds[NUM_FDS];
const int num_pollsets = 2; int fds[NUM_FDS];
const int num_closures = 4; test_pollset pollsets[NUM_POLLSETS];
test_fd tfds[num_fds]; grpc_closure closures[NUM_CLOSURES];
int fds[num_fds];
test_pollset pollsets[num_pollsets];
grpc_closure closures[num_closures];
int i; int i;
int result = 0; int result = 0;
test_fd_init(tfds, fds, num_fds); test_fd_init(tfds, fds, NUM_FDS);
test_pollset_init(pollsets, num_pollsets); test_pollset_init(pollsets, NUM_POLLSETS);
/* Two distinct polling islands, each with their own FD and pollset. */ /* Two distinct polling islands, each with their own FD and pollset. */
for (i = 0; i < num_fds; i++) { for (i = 0; i < NUM_FDS; i++) {
grpc_pollset_add_fd(&exec_ctx, pollsets[i].pollset, tfds[i].fd); grpc_pollset_add_fd(&exec_ctx, pollsets[i].pollset, tfds[i].fd);
grpc_exec_ctx_flush(&exec_ctx); grpc_exec_ctx_flush(&exec_ctx);
} }
@ -173,7 +175,7 @@ static void test_pollset_queue_merge_items() {
grpc_closure_init( grpc_closure_init(
closures + 3, increment, &result, closures + 3, increment, &result,
grpc_workqueue_scheduler(grpc_fd_get_polling_island(tfds[1].fd))); grpc_workqueue_scheduler(grpc_fd_get_polling_island(tfds[1].fd)));
for (i = 0; i < num_closures; ++i) { for (i = 0; i < NUM_CLOSURES; ++i) {
grpc_closure_sched(&exec_ctx, closures + i, GRPC_ERROR_NONE); grpc_closure_sched(&exec_ctx, closures + i, GRPC_ERROR_NONE);
} }
@ -186,7 +188,7 @@ static void test_pollset_queue_merge_items() {
* the merged polling island. * the merged polling island.
*/ */
grpc_pollset_worker *worker = NULL; grpc_pollset_worker *worker = NULL;
for (i = 0; i < num_closures; ++i) { for (i = 0; i < NUM_CLOSURES; ++i) {
const gpr_timespec deadline = gpr_time_add( const gpr_timespec deadline = gpr_time_add(
gpr_now(GPR_CLOCK_MONOTONIC), gpr_time_from_seconds(2, GPR_TIMESPAN)); gpr_now(GPR_CLOCK_MONOTONIC), gpr_time_from_seconds(2, GPR_TIMESPAN));
gpr_mu_lock(pollsets[1].mu); gpr_mu_lock(pollsets[1].mu);
@ -196,13 +198,17 @@ static void test_pollset_queue_merge_items() {
gpr_now(GPR_CLOCK_MONOTONIC), deadline)); gpr_now(GPR_CLOCK_MONOTONIC), deadline));
gpr_mu_unlock(pollsets[1].mu); gpr_mu_unlock(pollsets[1].mu);
} }
GPR_ASSERT(result == num_closures); GPR_ASSERT(result == NUM_CLOSURES);
test_fd_cleanup(&exec_ctx, tfds, num_fds); test_fd_cleanup(&exec_ctx, tfds, NUM_FDS);
test_pollset_cleanup(&exec_ctx, pollsets, num_pollsets); test_pollset_cleanup(&exec_ctx, pollsets, NUM_POLLSETS);
grpc_exec_ctx_finish(&exec_ctx); grpc_exec_ctx_finish(&exec_ctx);
} }
#undef NUM_FDS
#undef NUM_POLLSETS
#undef NUM_CLOSURES
/* /*
* Cases to test: * Cases to test:
* case 1) Polling islands of both fd and pollset are NULL * case 1) Polling islands of both fd and pollset are NULL
@ -213,18 +219,20 @@ static void test_pollset_queue_merge_items() {
* case 4.2) Polling islands of fd and pollset are NOT-equal (This results * case 4.2) Polling islands of fd and pollset are NOT-equal (This results
* in a merge) * in a merge)
* */ * */
#define NUM_FDS 8
#define NUM_POLLSETS 4
static void test_add_fd_to_pollset() { static void test_add_fd_to_pollset() {
grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
const int num_fds = 8; test_fd tfds[NUM_FDS];
const int num_pollsets = 4; int fds[NUM_FDS];
test_fd tfds[num_fds]; test_pollset pollsets[NUM_POLLSETS];
int fds[num_fds];
test_pollset pollsets[num_pollsets];
void *expected_pi = NULL; void *expected_pi = NULL;
int i; int i;
test_fd_init(tfds, fds, num_fds); test_fd_init(tfds, fds, NUM_FDS);
test_pollset_init(pollsets, num_pollsets); test_pollset_init(pollsets, NUM_POLLSETS);
/*Step 1. /*Step 1.
* Create three polling islands (This will exercise test case 1 and 2) with * Create three polling islands (This will exercise test case 1 and 2) with
@ -285,22 +293,25 @@ static void test_add_fd_to_pollset() {
/* Compare Fd:0's polling island with that of all other Fds */ /* Compare Fd:0's polling island with that of all other Fds */
expected_pi = grpc_fd_get_polling_island(tfds[0].fd); expected_pi = grpc_fd_get_polling_island(tfds[0].fd);
for (i = 1; i < num_fds; i++) { for (i = 1; i < NUM_FDS; i++) {
GPR_ASSERT(grpc_are_polling_islands_equal( GPR_ASSERT(grpc_are_polling_islands_equal(
expected_pi, grpc_fd_get_polling_island(tfds[i].fd))); expected_pi, grpc_fd_get_polling_island(tfds[i].fd)));
} }
/* Compare Fd:0's polling island with that of all other pollsets */ /* Compare Fd:0's polling island with that of all other pollsets */
for (i = 0; i < num_pollsets; i++) { for (i = 0; i < NUM_POLLSETS; i++) {
GPR_ASSERT(grpc_are_polling_islands_equal( GPR_ASSERT(grpc_are_polling_islands_equal(
expected_pi, grpc_pollset_get_polling_island(pollsets[i].pollset))); expected_pi, grpc_pollset_get_polling_island(pollsets[i].pollset)));
} }
test_fd_cleanup(&exec_ctx, tfds, num_fds); test_fd_cleanup(&exec_ctx, tfds, NUM_FDS);
test_pollset_cleanup(&exec_ctx, pollsets, num_pollsets); test_pollset_cleanup(&exec_ctx, pollsets, NUM_POLLSETS);
grpc_exec_ctx_finish(&exec_ctx); grpc_exec_ctx_finish(&exec_ctx);
} }
#undef NUM_FDS
#undef NUM_POLLSETS
int main(int argc, char **argv) { int main(int argc, char **argv) {
const char *poll_strategy = NULL; const char *poll_strategy = NULL;
grpc_test_init(argc, argv); grpc_test_init(argc, argv);

@ -484,6 +484,81 @@ TEST_P(AsyncEnd2endTest, SimpleClientStreaming) {
EXPECT_TRUE(recv_status.ok()); EXPECT_TRUE(recv_status.ok());
} }
// Two pings and a final pong.
TEST_P(AsyncEnd2endTest, SimpleClientStreamingWithCoalescingApi) {
ResetStub();
EchoRequest send_request;
EchoRequest recv_request;
EchoResponse send_response;
EchoResponse recv_response;
Status recv_status;
ClientContext cli_ctx;
ServerContext srv_ctx;
ServerAsyncReader<EchoResponse, EchoRequest> srv_stream(&srv_ctx);
send_request.set_message(GetParam().message_content);
cli_ctx.set_initial_metadata_corked(true);
// tag:1 never comes up since no op is performed
std::unique_ptr<ClientAsyncWriter<EchoRequest>> cli_stream(
stub_->AsyncRequestStream(&cli_ctx, &recv_response, cq_.get(), tag(1)));
service_.RequestRequestStream(&srv_ctx, &srv_stream, cq_.get(), cq_.get(),
tag(2));
cli_stream->Write(send_request, tag(3));
// 65536(64KB) is the default flow control window size. Should change this
// number when default flow control window size changes. For the write of
// send_request larger than the flow control window size, tag:3 will not come
// up until server read is initiated. For write of send_request smaller than
// the flow control window size, the request can take the free ride with
// initial metadata due to coalescing, thus write tag:3 will come up here.
if (GetParam().message_content.length() < 65536) {
Verifier(GetParam().disable_blocking)
.Expect(2, true)
.Expect(3, true)
.Verify(cq_.get());
} else {
Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
}
srv_stream.Read(&recv_request, tag(4));
if (GetParam().message_content.length() < 65536) {
Verifier(GetParam().disable_blocking).Expect(4, true).Verify(cq_.get());
} else {
Verifier(GetParam().disable_blocking)
.Expect(3, true)
.Expect(4, true)
.Verify(cq_.get());
}
EXPECT_EQ(send_request.message(), recv_request.message());
cli_stream->WriteLast(send_request, WriteOptions(), tag(5));
srv_stream.Read(&recv_request, tag(6));
Verifier(GetParam().disable_blocking)
.Expect(5, true)
.Expect(6, true)
.Verify(cq_.get());
EXPECT_EQ(send_request.message(), recv_request.message());
srv_stream.Read(&recv_request, tag(7));
Verifier(GetParam().disable_blocking).Expect(7, false).Verify(cq_.get());
send_response.set_message(recv_request.message());
srv_stream.Finish(send_response, Status::OK, tag(8));
cli_stream->Finish(&recv_status, tag(9));
Verifier(GetParam().disable_blocking)
.Expect(8, true)
.Expect(9, true)
.Verify(cq_.get());
EXPECT_EQ(send_response.message(), recv_response.message());
EXPECT_TRUE(recv_status.ok());
}
// One ping, two pongs. // One ping, two pongs.
TEST_P(AsyncEnd2endTest, SimpleServerStreaming) { TEST_P(AsyncEnd2endTest, SimpleServerStreaming) {
ResetStub(); ResetStub();
@ -540,6 +615,112 @@ TEST_P(AsyncEnd2endTest, SimpleServerStreaming) {
EXPECT_TRUE(recv_status.ok()); EXPECT_TRUE(recv_status.ok());
} }
// One ping, two pongs. Using WriteAndFinish API
TEST_P(AsyncEnd2endTest, SimpleServerStreamingWithCoalescingApiWAF) {
ResetStub();
EchoRequest send_request;
EchoRequest recv_request;
EchoResponse send_response;
EchoResponse recv_response;
Status recv_status;
ClientContext cli_ctx;
ServerContext srv_ctx;
ServerAsyncWriter<EchoResponse> srv_stream(&srv_ctx);
send_request.set_message(GetParam().message_content);
std::unique_ptr<ClientAsyncReader<EchoResponse>> cli_stream(
stub_->AsyncResponseStream(&cli_ctx, send_request, cq_.get(), tag(1)));
service_.RequestResponseStream(&srv_ctx, &recv_request, &srv_stream,
cq_.get(), cq_.get(), tag(2));
Verifier(GetParam().disable_blocking)
.Expect(1, true)
.Expect(2, true)
.Verify(cq_.get());
EXPECT_EQ(send_request.message(), recv_request.message());
send_response.set_message(recv_request.message());
srv_stream.Write(send_response, tag(3));
cli_stream->Read(&recv_response, tag(4));
Verifier(GetParam().disable_blocking)
.Expect(3, true)
.Expect(4, true)
.Verify(cq_.get());
EXPECT_EQ(send_response.message(), recv_response.message());
srv_stream.WriteAndFinish(send_response, WriteOptions(), Status::OK, tag(5));
cli_stream->Read(&recv_response, tag(6));
Verifier(GetParam().disable_blocking)
.Expect(5, true)
.Expect(6, true)
.Verify(cq_.get());
EXPECT_EQ(send_response.message(), recv_response.message());
cli_stream->Read(&recv_response, tag(7));
Verifier(GetParam().disable_blocking).Expect(7, false).Verify(cq_.get());
cli_stream->Finish(&recv_status, tag(8));
Verifier(GetParam().disable_blocking).Expect(8, true).Verify(cq_.get());
EXPECT_TRUE(recv_status.ok());
}
// One ping, two pongs. Using WriteLast API
TEST_P(AsyncEnd2endTest, SimpleServerStreamingWithCoalescingApiWL) {
ResetStub();
EchoRequest send_request;
EchoRequest recv_request;
EchoResponse send_response;
EchoResponse recv_response;
Status recv_status;
ClientContext cli_ctx;
ServerContext srv_ctx;
ServerAsyncWriter<EchoResponse> srv_stream(&srv_ctx);
send_request.set_message(GetParam().message_content);
std::unique_ptr<ClientAsyncReader<EchoResponse>> cli_stream(
stub_->AsyncResponseStream(&cli_ctx, send_request, cq_.get(), tag(1)));
service_.RequestResponseStream(&srv_ctx, &recv_request, &srv_stream,
cq_.get(), cq_.get(), tag(2));
Verifier(GetParam().disable_blocking)
.Expect(1, true)
.Expect(2, true)
.Verify(cq_.get());
EXPECT_EQ(send_request.message(), recv_request.message());
send_response.set_message(recv_request.message());
srv_stream.Write(send_response, tag(3));
cli_stream->Read(&recv_response, tag(4));
Verifier(GetParam().disable_blocking)
.Expect(3, true)
.Expect(4, true)
.Verify(cq_.get());
EXPECT_EQ(send_response.message(), recv_response.message());
srv_stream.WriteLast(send_response, WriteOptions(), tag(5));
cli_stream->Read(&recv_response, tag(6));
srv_stream.Finish(Status::OK, tag(7));
Verifier(GetParam().disable_blocking)
.Expect(5, true)
.Expect(6, true)
.Expect(7, true)
.Verify(cq_.get());
EXPECT_EQ(send_response.message(), recv_response.message());
cli_stream->Read(&recv_response, tag(8));
Verifier(GetParam().disable_blocking).Expect(8, false).Verify(cq_.get());
cli_stream->Finish(&recv_status, tag(9));
Verifier(GetParam().disable_blocking).Expect(9, true).Verify(cq_.get());
EXPECT_TRUE(recv_status.ok());
}
// One ping, one pong. // One ping, one pong.
TEST_P(AsyncEnd2endTest, SimpleBidiStreaming) { TEST_P(AsyncEnd2endTest, SimpleBidiStreaming) {
ResetStub(); ResetStub();
@ -599,6 +780,144 @@ TEST_P(AsyncEnd2endTest, SimpleBidiStreaming) {
EXPECT_TRUE(recv_status.ok()); EXPECT_TRUE(recv_status.ok());
} }
// One ping, one pong. Using server:WriteAndFinish api
TEST_P(AsyncEnd2endTest, SimpleBidiStreamingWithCoalescingApiWAF) {
ResetStub();
EchoRequest send_request;
EchoRequest recv_request;
EchoResponse send_response;
EchoResponse recv_response;
Status recv_status;
ClientContext cli_ctx;
ServerContext srv_ctx;
ServerAsyncReaderWriter<EchoResponse, EchoRequest> srv_stream(&srv_ctx);
send_request.set_message(GetParam().message_content);
cli_ctx.set_initial_metadata_corked(true);
std::unique_ptr<ClientAsyncReaderWriter<EchoRequest, EchoResponse>>
cli_stream(stub_->AsyncBidiStream(&cli_ctx, cq_.get(), tag(1)));
service_.RequestBidiStream(&srv_ctx, &srv_stream, cq_.get(), cq_.get(),
tag(2));
cli_stream->WriteLast(send_request, WriteOptions(), tag(3));
// 65536(64KB) is the default flow control window size. Should change this
// number when default flow control window size changes. For the write of
// send_request larger than the flow control window size, tag:3 will not come
// up until server read is initiated. For write of send_request smaller than
// the flow control window size, the request can take the free ride with
// initial metadata due to coalescing, thus write tag:3 will come up here.
if (GetParam().message_content.length() < 65536) {
Verifier(GetParam().disable_blocking)
.Expect(2, true)
.Expect(3, true)
.Verify(cq_.get());
} else {
Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
}
srv_stream.Read(&recv_request, tag(4));
if (GetParam().message_content.length() < 65536) {
Verifier(GetParam().disable_blocking).Expect(4, true).Verify(cq_.get());
} else {
Verifier(GetParam().disable_blocking)
.Expect(3, true)
.Expect(4, true)
.Verify(cq_.get());
}
EXPECT_EQ(send_request.message(), recv_request.message());
srv_stream.Read(&recv_request, tag(5));
Verifier(GetParam().disable_blocking).Expect(5, false).Verify(cq_.get());
send_response.set_message(recv_request.message());
srv_stream.WriteAndFinish(send_response, WriteOptions(), Status::OK, tag(6));
cli_stream->Read(&recv_response, tag(7));
Verifier(GetParam().disable_blocking)
.Expect(6, true)
.Expect(7, true)
.Verify(cq_.get());
EXPECT_EQ(send_response.message(), recv_response.message());
cli_stream->Finish(&recv_status, tag(8));
Verifier(GetParam().disable_blocking).Expect(8, true).Verify(cq_.get());
EXPECT_TRUE(recv_status.ok());
}
// One ping, one pong. Using server:WriteLast api
TEST_P(AsyncEnd2endTest, SimpleBidiStreamingWithCoalescingApiWL) {
ResetStub();
EchoRequest send_request;
EchoRequest recv_request;
EchoResponse send_response;
EchoResponse recv_response;
Status recv_status;
ClientContext cli_ctx;
ServerContext srv_ctx;
ServerAsyncReaderWriter<EchoResponse, EchoRequest> srv_stream(&srv_ctx);
send_request.set_message(GetParam().message_content);
cli_ctx.set_initial_metadata_corked(true);
std::unique_ptr<ClientAsyncReaderWriter<EchoRequest, EchoResponse>>
cli_stream(stub_->AsyncBidiStream(&cli_ctx, cq_.get(), tag(1)));
service_.RequestBidiStream(&srv_ctx, &srv_stream, cq_.get(), cq_.get(),
tag(2));
cli_stream->WriteLast(send_request, WriteOptions(), tag(3));
// 65536(64KB) is the default flow control window size. Should change this
// number when default flow control window size changes. For the write of
// send_request larger than the flow control window size, tag:3 will not come
// up until server read is initiated. For write of send_request smaller than
// the flow control window size, the request can take the free ride with
// initial metadata due to coalescing, thus write tag:3 will come up here.
if (GetParam().message_content.length() < 65536) {
Verifier(GetParam().disable_blocking)
.Expect(2, true)
.Expect(3, true)
.Verify(cq_.get());
} else {
Verifier(GetParam().disable_blocking).Expect(2, true).Verify(cq_.get());
}
srv_stream.Read(&recv_request, tag(4));
if (GetParam().message_content.length() < 65536) {
Verifier(GetParam().disable_blocking).Expect(4, true).Verify(cq_.get());
} else {
Verifier(GetParam().disable_blocking)
.Expect(3, true)
.Expect(4, true)
.Verify(cq_.get());
}
EXPECT_EQ(send_request.message(), recv_request.message());
srv_stream.Read(&recv_request, tag(5));
Verifier(GetParam().disable_blocking).Expect(5, false).Verify(cq_.get());
send_response.set_message(recv_request.message());
srv_stream.WriteLast(send_response, WriteOptions(), tag(6));
srv_stream.Finish(Status::OK, tag(7));
cli_stream->Read(&recv_response, tag(8));
Verifier(GetParam().disable_blocking)
.Expect(6, true)
.Expect(7, true)
.Expect(8, true)
.Verify(cq_.get());
EXPECT_EQ(send_response.message(), recv_response.message());
cli_stream->Finish(&recv_status, tag(9));
Verifier(GetParam().disable_blocking).Expect(9, true).Verify(cq_.get());
EXPECT_TRUE(recv_status.ok());
}
// Metadata tests // Metadata tests
TEST_P(AsyncEnd2endTest, ClientInitialMetadataRpc) { TEST_P(AsyncEnd2endTest, ClientInitialMetadataRpc) {
ResetStub(); ResetStub();

@ -702,6 +702,21 @@ TEST_P(End2endTest, RequestStreamOneRequest) {
EXPECT_TRUE(s.ok()); EXPECT_TRUE(s.ok());
} }
TEST_P(End2endTest, RequestStreamOneRequestWithCoalescingApi) {
ResetStub();
EchoRequest request;
EchoResponse response;
ClientContext context;
context.set_initial_metadata_corked(true);
auto stream = stub_->RequestStream(&context, &response);
request.set_message("hello");
stream->WriteLast(request, WriteOptions());
Status s = stream->Finish();
EXPECT_EQ(response.message(), request.message());
EXPECT_TRUE(s.ok());
}
TEST_P(End2endTest, RequestStreamTwoRequests) { TEST_P(End2endTest, RequestStreamTwoRequests) {
ResetStub(); ResetStub();
EchoRequest request; EchoRequest request;
@ -718,6 +733,22 @@ TEST_P(End2endTest, RequestStreamTwoRequests) {
EXPECT_TRUE(s.ok()); EXPECT_TRUE(s.ok());
} }
TEST_P(End2endTest, RequestStreamTwoRequestsWithCoalescingApi) {
ResetStub();
EchoRequest request;
EchoResponse response;
ClientContext context;
context.set_initial_metadata_corked(true);
auto stream = stub_->RequestStream(&context, &response);
request.set_message("hello");
EXPECT_TRUE(stream->Write(request));
stream->WriteLast(request, WriteOptions());
Status s = stream->Finish();
EXPECT_EQ(response.message(), "hellohello");
EXPECT_TRUE(s.ok());
}
TEST_P(End2endTest, ResponseStream) { TEST_P(End2endTest, ResponseStream) {
ResetStub(); ResetStub();
EchoRequest request; EchoRequest request;
@ -738,6 +769,27 @@ TEST_P(End2endTest, ResponseStream) {
EXPECT_TRUE(s.ok()); EXPECT_TRUE(s.ok());
} }
TEST_P(End2endTest, ResponseStreamWithCoalescingApi) {
ResetStub();
EchoRequest request;
EchoResponse response;
ClientContext context;
request.set_message("hello");
context.AddMetadata(kServerUseCoalescingApi, "1");
auto stream = stub_->ResponseStream(&context, request);
EXPECT_TRUE(stream->Read(&response));
EXPECT_EQ(response.message(), request.message() + "0");
EXPECT_TRUE(stream->Read(&response));
EXPECT_EQ(response.message(), request.message() + "1");
EXPECT_TRUE(stream->Read(&response));
EXPECT_EQ(response.message(), request.message() + "2");
EXPECT_FALSE(stream->Read(&response));
Status s = stream->Finish();
EXPECT_TRUE(s.ok());
}
TEST_P(End2endTest, BidiStream) { TEST_P(End2endTest, BidiStream) {
ResetStub(); ResetStub();
EchoRequest request; EchoRequest request;
@ -770,6 +822,39 @@ TEST_P(End2endTest, BidiStream) {
EXPECT_TRUE(s.ok()); EXPECT_TRUE(s.ok());
} }
TEST_P(End2endTest, BidiStreamWithCoalescingApi) {
ResetStub();
EchoRequest request;
EchoResponse response;
ClientContext context;
context.AddMetadata(kServerFinishAfterNReads, "3");
context.set_initial_metadata_corked(true);
grpc::string msg("hello");
auto stream = stub_->BidiStream(&context);
request.set_message(msg + "0");
EXPECT_TRUE(stream->Write(request));
EXPECT_TRUE(stream->Read(&response));
EXPECT_EQ(response.message(), request.message());
request.set_message(msg + "1");
EXPECT_TRUE(stream->Write(request));
EXPECT_TRUE(stream->Read(&response));
EXPECT_EQ(response.message(), request.message());
request.set_message(msg + "2");
stream->WriteLast(request, WriteOptions());
EXPECT_TRUE(stream->Read(&response));
EXPECT_EQ(response.message(), request.message());
EXPECT_FALSE(stream->Read(&response));
EXPECT_FALSE(stream->Read(&response));
Status s = stream->Finish();
EXPECT_TRUE(s.ok());
}
// Talk to the two services with the same name but different package names. // Talk to the two services with the same name but different package names.
// The two stubs are created on the same channel. // The two stubs are created on the same channel.
TEST_P(End2endTest, DiffPackageServices) { TEST_P(End2endTest, DiffPackageServices) {

@ -89,7 +89,7 @@ class MockClientReaderWriter<EchoRequest, EchoResponse> final
return true; return true;
} }
bool Write(const EchoRequest& msg, const WriteOptions& options) override { bool Write(const EchoRequest& msg, WriteOptions options) override {
gpr_log(GPR_INFO, "mock recv msg %s", msg.message().c_str()); gpr_log(GPR_INFO, "mock recv msg %s", msg.message().c_str());
last_message_ = msg.message(); last_message_ = msg.message();
return true; return true;

@ -246,6 +246,9 @@ Status TestServiceImpl::ResponseStream(ServerContext* context,
int server_try_cancel = GetIntValueFromMetadata( int server_try_cancel = GetIntValueFromMetadata(
kServerTryCancelRequest, context->client_metadata(), DO_NOT_CANCEL); kServerTryCancelRequest, context->client_metadata(), DO_NOT_CANCEL);
int server_coalescing_api = GetIntValueFromMetadata(
kServerUseCoalescingApi, context->client_metadata(), 0);
if (server_try_cancel == CANCEL_BEFORE_PROCESSING) { if (server_try_cancel == CANCEL_BEFORE_PROCESSING) {
ServerTryCancel(context); ServerTryCancel(context);
return Status::CANCELLED; return Status::CANCELLED;
@ -260,7 +263,11 @@ Status TestServiceImpl::ResponseStream(ServerContext* context,
for (int i = 0; i < kNumResponseStreamsMsgs; i++) { for (int i = 0; i < kNumResponseStreamsMsgs; i++) {
response.set_message(request->message() + grpc::to_string(i)); response.set_message(request->message() + grpc::to_string(i));
writer->Write(response); if (i == kNumResponseStreamsMsgs - 1 && server_coalescing_api != 0) {
writer->WriteLast(response, WriteOptions());
} else {
writer->Write(response);
}
} }
if (server_try_cancel_thd != nullptr) { if (server_try_cancel_thd != nullptr) {
@ -305,10 +312,21 @@ Status TestServiceImpl::BidiStream(
new std::thread(&TestServiceImpl::ServerTryCancel, this, context); new std::thread(&TestServiceImpl::ServerTryCancel, this, context);
} }
// kServerFinishAfterNReads suggests after how many reads, the server should
// write the last message and send status (coalesced using WriteLast)
int server_write_last = GetIntValueFromMetadata(
kServerFinishAfterNReads, context->client_metadata(), 0);
int read_counts = 0;
while (stream->Read(&request)) { while (stream->Read(&request)) {
read_counts++;
gpr_log(GPR_INFO, "recv msg %s", request.message().c_str()); gpr_log(GPR_INFO, "recv msg %s", request.message().c_str());
response.set_message(request.message()); response.set_message(request.message());
stream->Write(response); if (read_counts == server_write_last) {
stream->WriteLast(response, WriteOptions());
} else {
stream->Write(response);
}
} }
if (server_try_cancel_thd != nullptr) { if (server_try_cancel_thd != nullptr) {

@ -48,6 +48,8 @@ const int kNumResponseStreamsMsgs = 3;
const char* const kServerCancelAfterReads = "cancel_after_reads"; const char* const kServerCancelAfterReads = "cancel_after_reads";
const char* const kServerTryCancelRequest = "server_try_cancel"; const char* const kServerTryCancelRequest = "server_try_cancel";
const char* const kDebugInfoTrailerKey = "debug-info-bin"; const char* const kDebugInfoTrailerKey = "debug-info-bin";
const char* const kServerFinishAfterNReads = "server_finish_after_n_reads";
const char* const kServerUseCoalescingApi = "server_use_coalescing_api";
typedef enum { typedef enum {
DO_NOT_CANCEL = 0, DO_NOT_CANCEL = 0,

@ -240,6 +240,173 @@ static void BM_StreamingPingPongMsgs(benchmark::State& state) {
state.SetBytesProcessed(msg_size * state.iterations() * 2); state.SetBytesProcessed(msg_size * state.iterations() * 2);
} }
// Repeatedly makes Streaming Bidi calls (exchanging a configurable number of
// messages in each call) in a loop on a single channel. Different from
// BM_StreamingPingPong we are using stream coalescing api, e.g. WriteLast,
// WriteAndFinish, set_initial_metadata_corked. These apis aim at saving
// sendmsg syscalls for streaming by coalescing 1. initial metadata with first
// message; 2. final streaming message with trailing metadata.
//
// First parmeter (i.e state.range(0)): Message size (in bytes) to use
// Second parameter (i.e state.range(1)): Number of ping pong messages.
// Note: One ping-pong means two messages (one from client to server and
// the other from server to client):
// Third parameter (i.e state.range(2)): Switch between using WriteAndFinish
// API and WriteLast API for server.
template <class Fixture, class ClientContextMutator, class ServerContextMutator>
static void BM_StreamingPingPongWithCoalescingApi(benchmark::State& state) {
const int msg_size = state.range(0);
const int max_ping_pongs = state.range(1);
// This options is used to test out server API: WriteLast and WriteAndFinish
// respectively, since we can not use both of them on server side at the same
// time. Value 1 means we are testing out the WriteAndFinish API, and
// otherwise we are testing out the WriteLast API.
const int write_and_finish = state.range(2);
EchoTestService::AsyncService service;
std::unique_ptr<Fixture> fixture(new Fixture(&service));
{
EchoResponse send_response;
EchoResponse recv_response;
EchoRequest send_request;
EchoRequest recv_request;
if (msg_size > 0) {
send_request.set_message(std::string(msg_size, 'a'));
send_response.set_message(std::string(msg_size, 'b'));
}
std::unique_ptr<EchoTestService::Stub> stub(
EchoTestService::NewStub(fixture->channel()));
while (state.KeepRunning()) {
ServerContext svr_ctx;
ServerContextMutator svr_ctx_mut(&svr_ctx);
ServerAsyncReaderWriter<EchoResponse, EchoRequest> response_rw(&svr_ctx);
service.RequestBidiStream(&svr_ctx, &response_rw, fixture->cq(),
fixture->cq(), tag(0));
ClientContext cli_ctx;
ClientContextMutator cli_ctx_mut(&cli_ctx);
cli_ctx.set_initial_metadata_corked(true);
// tag:1 here will never comes up, since we are not performing any op due
// to initial metadata coalescing.
auto request_rw = stub->AsyncBidiStream(&cli_ctx, fixture->cq(), tag(1));
void* t;
bool ok;
int need_tags;
// Send 'max_ping_pongs' number of ping pong messages
int ping_pong_cnt = 0;
while (ping_pong_cnt < max_ping_pongs) {
if (ping_pong_cnt == max_ping_pongs - 1) {
request_rw->WriteLast(send_request, WriteOptions(), tag(2));
} else {
request_rw->Write(send_request, tag(2)); // Start client send
}
need_tags = (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
if (ping_pong_cnt == 0) {
// wait for the server call structure (call_hook, etc.) to be
// initialized (async stream between client side and server side
// established). It is necessary when client init metadata is
// coalesced
GPR_ASSERT(fixture->cq()->Next(&t, &ok));
while ((int)(intptr_t)t != 0) {
// In some cases tag:2 comes before tag:0 (write tag comes out
// first), this while loop is to make sure get tag:0.
int i = (int)(intptr_t)t;
GPR_ASSERT(need_tags & (1 << i));
need_tags &= ~(1 << i);
GPR_ASSERT(fixture->cq()->Next(&t, &ok));
}
}
response_rw.Read(&recv_request, tag(3)); // Start server recv
request_rw->Read(&recv_response, tag(4)); // Start client recv
while (need_tags) {
GPR_ASSERT(fixture->cq()->Next(&t, &ok));
GPR_ASSERT(ok);
int i = (int)(intptr_t)t;
// If server recv is complete, start the server send operation
if (i == 3) {
if (ping_pong_cnt == max_ping_pongs - 1) {
if (write_and_finish == 1) {
response_rw.WriteAndFinish(send_response, WriteOptions(),
Status::OK, tag(5));
} else {
response_rw.WriteLast(send_response, WriteOptions(), tag(5));
// WriteLast buffers the write, so neither server write op nor
// client read op will finish inside the while loop.
need_tags &= ~(1 << 4);
need_tags &= ~(1 << 5);
}
} else {
response_rw.Write(send_response, tag(5));
}
}
GPR_ASSERT(need_tags & (1 << i));
need_tags &= ~(1 << i);
}
ping_pong_cnt++;
}
if (max_ping_pongs == 0) {
need_tags = (1 << 6) | (1 << 7) | (1 << 8);
} else {
if (write_and_finish == 1) {
need_tags = (1 << 8);
} else {
// server's buffered write and the client's read of the buffered write
// tags should come up.
need_tags = (1 << 4) | (1 << 5) | (1 << 7) | (1 << 8);
}
}
// No message write or initial metadata write happened yet.
if (max_ping_pongs == 0) {
request_rw->WritesDone(tag(6));
// wait for server call data structure(call_hook, etc.) to be
// initialized, since initial metadata is corked.
GPR_ASSERT(fixture->cq()->Next(&t, &ok));
while ((int)(intptr_t)t != 0) {
int i = (int)(intptr_t)t;
GPR_ASSERT(need_tags & (1 << i));
need_tags &= ~(1 << i);
GPR_ASSERT(fixture->cq()->Next(&t, &ok));
}
response_rw.Finish(Status::OK, tag(7));
} else {
if (write_and_finish != 1) {
response_rw.Finish(Status::OK, tag(7));
}
}
Status recv_status;
request_rw->Finish(&recv_status, tag(8));
while (need_tags) {
GPR_ASSERT(fixture->cq()->Next(&t, &ok));
int i = (int)(intptr_t)t;
GPR_ASSERT(need_tags & (1 << i));
need_tags &= ~(1 << i);
}
GPR_ASSERT(recv_status.ok());
}
}
fixture->Finish(state);
fixture.reset();
state.SetBytesProcessed(msg_size * state.iterations() * max_ping_pongs * 2);
}
/******************************************************************************* /*******************************************************************************
* CONFIGURATIONS * CONFIGURATIONS
*/ */
@ -270,6 +437,30 @@ BENCHMARK_TEMPLATE(BM_StreamingPingPongMsgs, InProcessCHTTP2, NoOpMutator,
BENCHMARK_TEMPLATE(BM_StreamingPingPongMsgs, TCP, NoOpMutator, NoOpMutator) BENCHMARK_TEMPLATE(BM_StreamingPingPongMsgs, TCP, NoOpMutator, NoOpMutator)
->Range(0, 128 * 1024 * 1024); ->Range(0, 128 * 1024 * 1024);
// Generate Args for StreamingPingPongWithCoalescingApi benchmarks. Currently
// generates args for only "small streams" (i.e streams with 0, 1 or 2 messages)
static void StreamingPingPongWithCoalescingApiArgs(
benchmark::internal::Benchmark* b) {
int msg_size = 0;
b->Args(
{0, 0, 0}); // spl case: 0 ping-pong msgs (msg_size doesn't matter here)
b->Args(
{0, 0, 1}); // spl case: 0 ping-pong msgs (msg_size doesn't matter here)
for (msg_size = 0; msg_size <= 128 * 1024 * 1024;
msg_size == 0 ? msg_size++ : msg_size *= 8) {
b->Args({msg_size, 1, 0});
b->Args({msg_size, 2, 0});
b->Args({msg_size, 1, 1});
b->Args({msg_size, 2, 1});
}
}
BENCHMARK_TEMPLATE(BM_StreamingPingPongWithCoalescingApi, InProcessCHTTP2,
NoOpMutator, NoOpMutator)
->Apply(StreamingPingPongWithCoalescingApiArgs);
} // namespace testing } // namespace testing
} // namespace grpc } // namespace grpc

@ -111,6 +111,8 @@ _EXEMPT = frozenset((
# An older file originally from outside gRPC. # An older file originally from outside gRPC.
'src/php/tests/bootstrap.php', 'src/php/tests/bootstrap.php',
# census.proto copied from github
'tools/grpcz/census.proto',
)) ))

@ -0,0 +1,63 @@
# Copyright 2017, 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.
licenses(["notice"]) # 3-clause BSD
package(default_visibility = ["//visibility:public"])
load("//:bazel/grpc_build_system.bzl", "grpc_proto_library")
grpc_proto_library (
name = "monitoring_proto",
srcs = [
"monitoring.proto",
],
deps = [
":census_proto",
],
well_known_protos = "@submodule_protobuf//:well_known_protos",
)
grpc_proto_library (
name = "census_proto",
srcs = [
"census.proto",
],
well_known_protos = "@submodule_protobuf//:well_known_protos",
)
cc_binary(
name = "grpcz_client",
srcs = ["grpcz_client.cc",],
deps = [
"//external:gflags",
"monitoring_proto",
"@mongoose_repo//:mongoose_lib",
],
)

@ -0,0 +1,318 @@
// Copyright 2017, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//TODO(ericgribkoff) Depend on this directly from the instrumentation-proto
//repository.
syntax = "proto3";
package google.instrumentation;
option java_package = "com.google.instrumentation.stats.proto";
option java_outer_classname = "CensusProto";
// All the census protos.
//
// Nomenclature notes:
// * Capitalized names below (like View) are protos.
// * Protos which describe types are named with a Descriptor suffix (e.g.
// MesurementDescriptor).
//
// Census lets you define the type and description of the data being measured
// (e.g. the latency of an RPC or the number of CPU cycles spent on an
// operation using MeasurementDescriptor. As individual measurements (a double
// value) for are recorded, they are aggregated together into an
// Aggregation. There are two Aggregation types available: Distribution
// (describes the distribution of all measurements, possibly with a histogram)
// and IntervalStats (the count and mean of measurements across specified time
// periods). An Aggregation is described by an AggregationDescriptor.
//
// You can define how your measurements (described by a MeasurementDescriptor)
// are broken down by Tag values and which Aggregations to use through a
// ViewDescriptor. The output (all measurements broken down by tag values into
// specific Aggregations) is called a View.
// The following two types are copied from
// google/protobuf/{duration,timestamp}.proto. Ideally, we would be able to
// import them, but this causes compilation issues on C-based systems
// (e.g. https://koti.kapsi.fi/jpa/nanopb/), which cannot process the C++
// headers generated from the standard protobuf distribution. See the relevant
// proto files for full documentation of these types.
message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive.
int64 seconds = 1;
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
int32 nanos = 2;
}
message Timestamp {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
int64 seconds = 1;
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
int32 nanos = 2;
}
// MeasurementDescriptor describes a data point (measurement) type.
message MeasurementDescriptor {
// A descriptive name, e.g. rpc_latency, cpu. Must be unique.
string name = 1;
// More detailed description of the resource, used in documentation.
string description = 2;
// Fundamental units of measurement supported by Census
// TODO(aveitch): expand this to include other S.I. units?
enum BasicUnit {
UNKNOWN = 0; // Implementations should not use this
SCALAR = 1; // Dimensionless
BITS = 2; // A single bit
BYTES = 3; // An 8-bit byte
SECONDS = 4; // S.I. unit
CORES = 5; // CPU core usage
MAX_UNITS = 6; // Last defined value; implementations should only use
// this for validation.
}
// MeasurementUnit lets you build compound units of the form
// 10^n * (A * B * ...) / (X * Y * ...),
// where the elements in the numerator and denominator are all BasicUnits. A
// MeasurementUnit must have at least one BasicUnit in its numerator.
//
// To specify multiplication in the numerator or denominator, simply specify
// multiple numerator or denominator fields. For example:
//
// - byte-seconds (i.e. bytes * seconds):
// numerator: BYTES
// numerator: SECS
//
// - events/sec^2 (i.e. rate of change of events/sec):
// numerator: SCALAR
// denominator: SECS
// denominator: SECS
//
// To specify multiples (in power of 10) of units, specify a non-zero
// 'power10' value, for example:
//
// - MB/s (i.e. megabytes / s):
// power10: 6
// numerator: BYTES
// denominator: SECS
//
// - nanoseconds
// power10: -9
// numerator: SECS
message MeasurementUnit {
int32 power10 = 1;
repeated BasicUnit numerators = 2;
repeated BasicUnit denominators = 3;
}
// The units used by this type of measurement.
MeasurementUnit unit = 3;
}
// An aggregation summarizes a series of individual measurements. There are
// two types of aggregation (IntervalAggregation and DistributionAggregation),
// unique types of each can be set using descriptors for each.
// DistributionAggregation contains summary statistics for a population of
// values and, optionally, a histogram representing the distribution of those
// values across a specified set of histogram buckets, as defined in
// DistributionAggregationDescriptor.bucket_bounds.
//
// The summary statistics are the count, mean, minimum, and the maximum of the
// set of population of values.
//
// Although it is not forbidden, it is generally a bad idea to include
// non-finite values (infinities or NaNs) in the population of values, as this
// will render the `mean` field meaningless.
message DistributionAggregation {
// The number of values in the population. Must be non-negative.
int64 count = 1;
// The arithmetic mean of the values in the population. If `count` is zero
// then this field must be zero.
double mean = 2;
// The sum of the values in the population. If `count` is zero then this
// field must be zero.
double sum = 3;
// Describes a range of population values.
message Range {
// The minimum of the population values.
double min = 1;
// The maximum of the population values.
double max = 2;
}
// The range of the population values. If `count` is zero, this field will not
// be defined.
Range range = 4;
// A Distribution may optionally contain a histogram of the values in the
// population. The histogram is given in `bucket_count` as counts of values
// that fall into one of a sequence of non-overlapping buckets, as described
// by `DistributionAggregationDescriptor.bucket_boundaries`. The sum of the
// values in `bucket_counts` must equal the value in `count`.
//
// Bucket counts are given in order under the numbering scheme described
// above (the underflow bucket has number 0; the finite buckets, if any,
// have numbers 1 through N-2; the overflow bucket has number N-1).
//
// The size of `bucket_count` must be no greater than N as defined in
// `bucket_boundaries`.
//
// Any suffix of trailing zero bucket_count fields may be omitted.
repeated int64 bucket_counts = 5;
// Tags associated with this DistributionAggregation. These will be filled
// in based on the View specification.
repeated Tag tags = 6;
}
message DistributionAggregationDescriptor {
// A Distribution may optionally contain a histogram of the values in the
// population. The bucket boundaries for that histogram are described by
// `bucket_bounds`. This defines `size(bucket_bounds) + 1` (= N)
// buckets. The boundaries for bucket index i are:
//
// [-infinity, bucket_bounds[i]) for i == 0
// [bucket_bounds[i-1], bucket_bounds[i]) for 0 < i < N-2
// [bucket_bounds[i-1], +infinity) for i == N-1
//
// i.e. an underflow bucket (number 0), zero or more finite buckets (1
// through N - 2, and an overflow bucket (N - 1), with inclusive lower
// bounds and exclusive upper bounds.
//
// If `bucket_bounds` has no elements (zero size), then there is no
// histogram associated with the Distribution. If `bucket_bounds` has only
// one element, there are no finite buckets, and that single element is the
// common boundary of the overflow and underflow buckets. The values must
// be monotonically increasing.
repeated double bucket_bounds = 1;
}
// An IntervalAggreation records summary stats over various time
// windows. These stats are approximate, with the degree of accuracy
// controlled by setting the n_sub_intervals parameter in the
// IntervalAggregationDescriptor.
message IntervalAggregation {
// Summary statistic over a single time interval.
message Interval {
// The interval duration. Must be positive.
Duration interval_size = 1;
// Approximate number of measurements recorded in this interval.
double count = 2;
// The cumulative sum of measurements in this interval.
double sum = 3;
}
// Full set of intervals for this aggregation.
repeated Interval intervals = 1;
// Tags associated with this IntervalAggregation. These will be filled in
// based on the View specification.
repeated Tag tags = 2;
}
// An IntervalAggreationDescriptor specifies time intervals for an
// IntervalAggregation.
message IntervalAggregationDescriptor {
// Number of internal sub-intervals to use when collecting stats for each
// interval. The max error in interval measurements will be approximately
// 1/n_sub_intervals (although in practice, this will only be approached in
// the presence of very large and bursty workload changes), and underlying
// memory usage will be roughly proportional to the value of this
// field. Must be in the range [2, 20]. A value of 5 will be used if this is
// unspecified.
int32 n_sub_intervals = 1;
// The size of each interval, as a time duration. Must have at least one
// element.
repeated Duration interval_sizes = 2;
}
// A Tag: key-value pair.
message Tag {
string key = 1;
string value = 2;
}
// A ViewDescriptor specifies an AggregationDescriptor and a set of tag
// keys. Views instantiated from this descriptor will contain Aggregations
// broken down by the unique set of matching tag values for each measurement.
message ViewDescriptor {
// Name of view. Must be unique.
string name = 1;
// More detailed description, for documentation purposes.
string description = 2;
// Name of a MeasurementDescriptor to be used for this view.
string measurement_descriptor_name = 3;
// Aggregation type to associate with View.
oneof aggregation {
IntervalAggregationDescriptor interval_aggregation = 4;
DistributionAggregationDescriptor distribution_aggregation = 5;
}
// Tag keys to match with a given measurement. If no keys are specified,
// then all stats are recorded. Keys must be unique.
repeated string tag_keys = 6;
}
// DistributionView contains all aggregations for a view specified using a
// DistributionAggregationDescriptor.
message DistributionView {
// Aggregations - each will have a unique set of tag values for the tag_keys
// associated with the corresponding View.
repeated DistributionAggregation aggregations = 1;
// Start and end timestamps over which aggregations was accumulated.
Timestamp start = 2;
Timestamp end = 3;
}
// IntervalView contains all aggregations for a view specified using a
// IntervalAggregationDescriptor.
message IntervalView {
// Aggregations - each will have a unique set of tag values for the tag_keys
// associated with the corresponding View.
repeated IntervalAggregation aggregations = 1;
}
// A View contains the aggregations based on a ViewDescriptor.
message View {
// ViewDescriptor name associated with this set of View.
string view_name = 1;
oneof view {
DistributionView distribution_view = 2;
IntervalView interval_view = 3;
}
}

@ -0,0 +1,181 @@
/*
*
* Copyright 2017, 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 <string>
#include <google/protobuf/util/json_util.h>
#include <grpc++/grpc++.h>
#include <grpc/support/log.h>
#include "gflags/gflags.h"
#include "mongoose.h"
// TODO (makdharma): remove local copies of these protos
#include "tools/grpcz/census.grpc.pb.h"
#include "tools/grpcz/monitoring.grpc.pb.h"
DEFINE_string(
grpcz_server, "127.0.0.1:8080",
"Unix domain socket path (e.g. unix://tmp/grpcz.sock) or IP address"
"(host:port) where grpcz server is running.");
DEFINE_string(http_port, "8000",
"Port id for accessing the HTTP server that renders /grpcz page");
DEFINE_bool(print_to_console, false,
"print the JSON retreived from grpcz server and quit");
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using ::grpc::instrumentation::v1alpha::CanonicalRpcStats;
using ::grpc::instrumentation::v1alpha::Monitoring;
static const std::string static_html_header =
"<!DOCTYPE html> <html> <head> <style> \
table { border-collapse: collapse; width: 100%; } \
table, td, th { border: 1px solid black; } \
</style> </head> <body>\
<div id='stats' data-stats='";
static const std::string static_html_footer =
"' class='hidden'></div>\
<h1>GRPCZ Statistics</h1> <div id='table'> </div> \
<script> \
var canonical_stats = JSON.parse(\
document.getElementById('stats').getAttribute('data-stats')); \
var table = document.createElement('table'); \
if (canonical_stats['Error Message'] != undefined) { \
document.getElementById('table').innerHTML = canonical_stats['Error Message']; } \
else {\
for (var key in canonical_stats) { \
name = canonical_stats[key]['view']['viewName']; \
distribution = canonical_stats[key]['view']['distributionView']; \
interval = canonical_stats[key]['view']['intervalView']; \
value = (interval == undefined) ? \
JSON.stringify(distribution, null, ' ') : \
JSON.stringify(interval, null, ' '); \
var row = table.insertRow(-1); \
var col1 = row.insertCell(0); \
var col2 = row.insertCell(1); \
col1.innerHTML = name; \
col2.innerHTML = '<pre>' + value + '</pre>'; \
} \
document.getElementById('table').appendChild(table); \
}\
</script> </body> </html>";
class GrpczClient {
public:
GrpczClient(std::shared_ptr<Channel> channel)
: stub_(Monitoring::NewStub(channel)) {}
std::string GetStatsAsJson() {
const ::google::protobuf::Empty request;
CanonicalRpcStats reply;
ClientContext context;
Status status = stub_->GetCanonicalRpcStats(&context, request, &reply);
if (status.ok()) {
std::string json_str;
::google::protobuf::util::MessageToJsonString(reply, &json_str);
return json_str;
} else {
static const std::string error_message_json =
"{\"Error Message\":\"" + status.error_message() + "\"}";
gpr_log(GPR_DEBUG, "%d: %s", status.error_code(),
status.error_message().c_str());
return error_message_json;
}
}
private:
std::unique_ptr<Monitoring::Stub> stub_;
};
static struct mg_serve_http_opts s_http_server_opts;
std::unique_ptr<GrpczClient> g_grpcz_client;
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
if (ev == MG_EV_HTTP_REQUEST) {
mg_serve_http(nc, (struct http_message *)p, s_http_server_opts);
}
}
static void grpcz_handler(struct mg_connection *nc, int ev, void *ev_data) {
(void)ev;
(void)ev_data;
gpr_log(GPR_INFO, "fetching grpcz stats from %s", FLAGS_grpcz_server.c_str());
std::string json_str = g_grpcz_client->GetStatsAsJson();
std::string rendered_html =
static_html_header + json_str + static_html_footer;
mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n%s", rendered_html.c_str());
nc->flags |= MG_F_SEND_AND_CLOSE;
}
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
// Create a client
g_grpcz_client.reset(new GrpczClient(grpc::CreateChannel(
FLAGS_grpcz_server, grpc::InsecureChannelCredentials())));
if (FLAGS_print_to_console) {
// using GPR_ERROR since this is the default verbosity. _DEBUG or _INFO
// won't print unless GRPC_VERBOSITY env var is set appropriately, which
// might confuse users of this utility.
gpr_log(GPR_ERROR, "%s\n", g_grpcz_client->GetStatsAsJson().c_str());
return 0;
}
// Set up a mongoose webserver handler
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL);
gpr_log(GPR_INFO, "Starting grpcz web server on port %s\n",
FLAGS_http_port.c_str());
struct mg_connection *nc = mg_bind(&mgr, FLAGS_http_port.c_str(), ev_handler);
if (nc == NULL) {
gpr_log(GPR_ERROR, "Failed to create listener on port %s\n",
FLAGS_http_port.c_str());
return -1;
}
mg_register_http_endpoint(nc, "/grpcz", grpcz_handler);
mg_set_protocol_http_websocket(nc);
// Poll in a loop and serve /grpcz pages
for (;;) {
static const int k_sleep_millis = 100;
mg_mgr_poll(&mgr, k_sleep_millis);
}
mg_mgr_free(&mgr);
return 0;
}

@ -0,0 +1,156 @@
// Copyright 2017, 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 file defines an interface for exporting monitoring information
// out of gRPC servers.
syntax = "proto3";
// TODO(ericgribkoff) Figure out how to manage the external Census proto
// dependency.
import "tools/grpcz/census.proto";
import "google/protobuf/any.proto";
import "google/protobuf/empty.proto";
package grpc.instrumentation.v1alpha;
option java_multiple_files = true;
option java_package = "io.grpc.instrumentation.v1alpha";
option java_outer_classname = "MonitoringProto";
service Monitoring {
// Return canonical RPC stats
rpc GetCanonicalRpcStats(google.protobuf.Empty) returns (CanonicalRpcStats) {
}
// Query the server for specific stats
rpc GetStats(StatsRequest) returns (StatsResponse) {
}
// Request the server to stream back snapshots of the requested stats
rpc WatchStats(StatsRequest) returns (stream StatsResponse) {
}
// Return request traces.
rpc GetRequestTraces(TraceRequest) returns(TraceResponse) {
// TODO(aveitch): Please define the messages here
}
// Return application-defined groups of monitoring data.
// This is a low level facility to allow extension of the monitoring API to
// application-specific monitoring data. Frameworks may use this to define
// additional groups of monitoring data made available by servers.
rpc GetCustomMonitoringData(MonitoringDataGroup)
returns (CustomMonitoringData) {
}
}
// Canonical RPC stats exported by gRPC.
message CanonicalRpcStats {
StatsResponse rpc_client_errors = 1;
StatsResponse rpc_client_completed_rpcs = 2;
StatsResponse rpc_client_started_rpcs = 3;
StatsResponse rpc_client_elapsed_time = 4;
StatsResponse rpc_client_server_elapsed_time = 5;
StatsResponse rpc_client_request_bytes = 6;
StatsResponse rpc_client_response_bytes = 7;
StatsResponse rpc_client_request_count = 8;
StatsResponse rpc_client_response_count = 9;
StatsResponse rpc_server_errors = 10;
StatsResponse rpc_server_completed_rpcs = 11;
StatsResponse rpc_server_server_elapsed_time = 12;
StatsResponse rpc_server_request_bytes = 13;
StatsResponse rpc_server_response_bytes = 14;
StatsResponse rpc_server_request_count = 15;
StatsResponse rpc_server_response_count = 16;
StatsResponse rpc_server_elapsed_time = 17;
//TODO(ericgribkoff) Add minute-hour interval stats.
}
// This message is sent when requesting a set of stats (Census Views) from
// a client system, as part of the MonitoringService API's.
message StatsRequest {
// An optional set of ViewDescriptor names. Only Views using these
// descriptors will be sent back in the response. If no names are provided,
// then all Views present in the client system will be included in every
// response. If measurement_names is also provided, then Views matching the
// intersection of the two are returned.
// TODO(aveitch): Consider making this a list of regexes or prefix matches in
// the future.
repeated string view_names = 1;
// An optional set of MeasurementDescriptor names. Only Views using these
// descriptors will be sent back in the response. If no names are provided,
// then all Views present in the client system will be included in every
// response. If view_names is also provided, then Views matching the
// intersection of the two are returned.
// TODO(aveitch): Consider making this a list of regexes or prefix matches in
// the future.
repeated string measurement_names = 2;
// By default, the MeasurementDescriptors and ViewDescriptors corresponding to
// the Views that are returned in a StatsResponse will be included in the
// first such response. Set this to true to have them not sent.
bool dont_include_descriptors_in_first_response = 3;
}
// This message contains all information relevant to a single View. It is the
// return type for GetStats and WatchStats, and used in CanonicalRpcStats.
message StatsResponse {
// A StatsResponse can optionally contain the MeasurementDescriptor and
// ViewDescriptor for the View. These will be sent in the first WatchStats
// response, or all GetStats and GetCanonicalRpcStats responses. These will
// not be set for {Get,Watch}Stats if
// dont_include_descriptors_in_first_response is set to true in the
// StatsRequest.
google.instrumentation.MeasurementDescriptor measurement_descriptor = 1;
google.instrumentation.ViewDescriptor view_descriptor = 2;
// The View data.
google.instrumentation.View view = 3;
}
message TraceRequest {
// TODO(aveitch): Complete definition of this type
}
message TraceResponse {
// TODO(aveitch): Complete definition of this type
}
message MonitoringDataGroup {
string name = 1; // name of a group of monitoring data
}
// The wrapper for custom monitoring data.
message CustomMonitoringData {
// can be any application specific monitoring data
google.protobuf.Any contents = 1;
}

@ -3810,7 +3810,6 @@
"test/cpp/interop/stress_interop_client.cc", "test/cpp/interop/stress_interop_client.cc",
"test/cpp/interop/stress_interop_client.h", "test/cpp/interop/stress_interop_client.h",
"test/cpp/interop/stress_test.cc", "test/cpp/interop/stress_test.cc",
"test/cpp/util/create_test_channel.cc",
"test/cpp/util/create_test_channel.h", "test/cpp/util/create_test_channel.h",
"test/cpp/util/metrics_server.cc", "test/cpp/util/metrics_server.cc",
"test/cpp/util/metrics_server.h" "test/cpp/util/metrics_server.h"

@ -83123,6 +83123,29 @@
], ],
"uses_polling": false "uses_polling": false
}, },
{
"args": [
"test/core/end2end/fuzzers/api_fuzzer_corpus/crash-59a56fa18034a104fb9f16cd58071b6ff93b8756"
],
"ci_platforms": [
"linux"
],
"cpu_cost": 0.1,
"exclude_configs": [
"tsan"
],
"exclude_iomgrs": [
"uv"
],
"flaky": false,
"language": "c",
"name": "api_fuzzer_one_entry",
"platforms": [
"mac",
"linux"
],
"uses_polling": false
},
{ {
"args": [ "args": [
"test/core/end2end/fuzzers/api_fuzzer_corpus/crash-59b587d15c0bcdb985417cd7a133cecfcc232698" "test/core/end2end/fuzzers/api_fuzzer_corpus/crash-59b587d15c0bcdb985417cd7a133cecfcc232698"

@ -205,8 +205,6 @@
</ClCompile> </ClCompile>
<ClCompile Include="$(SolutionDir)\..\test\cpp\interop\stress_test.cc"> <ClCompile Include="$(SolutionDir)\..\test\cpp\interop\stress_test.cc">
</ClCompile> </ClCompile>
<ClCompile Include="$(SolutionDir)\..\test\cpp\util\create_test_channel.cc">
</ClCompile>
<ClCompile Include="$(SolutionDir)\..\test\cpp\util\metrics_server.cc"> <ClCompile Include="$(SolutionDir)\..\test\cpp\util\metrics_server.cc">
</ClCompile> </ClCompile>
</ItemGroup> </ItemGroup>

@ -22,9 +22,6 @@
<ClCompile Include="$(SolutionDir)\..\test\cpp\interop\stress_test.cc"> <ClCompile Include="$(SolutionDir)\..\test\cpp\interop\stress_test.cc">
<Filter>test\cpp\interop</Filter> <Filter>test\cpp\interop</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="$(SolutionDir)\..\test\cpp\util\create_test_channel.cc">
<Filter>test\cpp\util</Filter>
</ClCompile>
<ClCompile Include="$(SolutionDir)\..\test\cpp\util\metrics_server.cc"> <ClCompile Include="$(SolutionDir)\..\test\cpp\util\metrics_server.cc">
<Filter>test\cpp\util</Filter> <Filter>test\cpp\util</Filter>
</ClCompile> </ClCompile>

Loading…
Cancel
Save