Merge pull request #17104 from vjpai/callback_streaming

C++: Experimental client callback streaming API
pull/17144/head
Vijay Pai 6 years ago committed by GitHub
commit 53949b7d76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      include/grpcpp/generic/generic_stub.h
  2. 8
      include/grpcpp/impl/codegen/callback_common.h
  3. 12
      include/grpcpp/impl/codegen/channel_interface.h
  4. 641
      include/grpcpp/impl/codegen/client_callback.h
  5. 12
      include/grpcpp/impl/codegen/client_context.h
  6. 78
      src/compiler/cpp_generator.cc
  7. 9
      src/cpp/client/generic_stub.cc
  8. 7
      test/cpp/codegen/compiler_test_golden
  9. 218
      test/cpp/end2end/client_callback_end2end_test.cc
  10. 1
      test/cpp/end2end/test_service_impl.cc

@ -24,6 +24,7 @@
#include <grpcpp/support/async_stream.h> #include <grpcpp/support/async_stream.h>
#include <grpcpp/support/async_unary_call.h> #include <grpcpp/support/async_unary_call.h>
#include <grpcpp/support/byte_buffer.h> #include <grpcpp/support/byte_buffer.h>
#include <grpcpp/support/client_callback.h>
#include <grpcpp/support/status.h> #include <grpcpp/support/status.h>
namespace grpc { namespace grpc {
@ -76,6 +77,10 @@ class GenericStub final {
const ByteBuffer* request, ByteBuffer* response, const ByteBuffer* request, ByteBuffer* response,
std::function<void(Status)> on_completion); std::function<void(Status)> on_completion);
void PrepareBidiStreamingCall(
ClientContext* context, const grpc::string& method,
experimental::ClientBidiReactor<ByteBuffer, ByteBuffer>* reactor);
private: private:
GenericStub* stub_; GenericStub* stub_;
}; };

@ -145,18 +145,19 @@ class CallbackWithSuccessTag
// or on a tag that has been Set before unless the tag has been cleared. // or on a tag that has been Set before unless the tag has been cleared.
void Set(grpc_call* call, std::function<void(bool)> f, void Set(grpc_call* call, std::function<void(bool)> f,
CompletionQueueTag* ops) { CompletionQueueTag* ops) {
GPR_CODEGEN_ASSERT(call_ == nullptr);
g_core_codegen_interface->grpc_call_ref(call);
call_ = call; call_ = call;
func_ = std::move(f); func_ = std::move(f);
ops_ = ops; ops_ = ops;
g_core_codegen_interface->grpc_call_ref(call);
functor_run = &CallbackWithSuccessTag::StaticRun; functor_run = &CallbackWithSuccessTag::StaticRun;
} }
void Clear() { void Clear() {
if (call_ != nullptr) { if (call_ != nullptr) {
func_ = nullptr;
grpc_call* call = call_; grpc_call* call = call_;
call_ = nullptr; call_ = nullptr;
func_ = nullptr;
g_core_codegen_interface->grpc_call_unref(call); g_core_codegen_interface->grpc_call_unref(call);
} }
} }
@ -182,10 +183,9 @@ class CallbackWithSuccessTag
} }
void Run(bool ok) { void Run(bool ok) {
void* ignored = ops_; void* ignored = ops_;
bool new_ok = ok;
// Allow a "false" return value from FinalizeResult to silence the // Allow a "false" return value from FinalizeResult to silence the
// callback, just as it silences a CQ tag in the async cases // callback, just as it silences a CQ tag in the async cases
bool do_callback = ops_->FinalizeResult(&ignored, &new_ok); bool do_callback = ops_->FinalizeResult(&ignored, &ok);
GPR_CODEGEN_ASSERT(ignored == ops_); GPR_CODEGEN_ASSERT(ignored == ops_);
if (do_callback) { if (do_callback) {

@ -53,6 +53,12 @@ template <class W, class R>
class ClientAsyncReaderWriterFactory; class ClientAsyncReaderWriterFactory;
template <class R> template <class R>
class ClientAsyncResponseReaderFactory; class ClientAsyncResponseReaderFactory;
template <class W, class R>
class ClientCallbackReaderWriterFactory;
template <class R>
class ClientCallbackReaderFactory;
template <class W>
class ClientCallbackWriterFactory;
class InterceptedChannel; class InterceptedChannel;
} // namespace internal } // namespace internal
@ -106,6 +112,12 @@ class ChannelInterface {
friend class ::grpc::internal::ClientAsyncReaderWriterFactory; friend class ::grpc::internal::ClientAsyncReaderWriterFactory;
template <class R> template <class R>
friend class ::grpc::internal::ClientAsyncResponseReaderFactory; friend class ::grpc::internal::ClientAsyncResponseReaderFactory;
template <class W, class R>
friend class ::grpc::internal::ClientCallbackReaderWriterFactory;
template <class R>
friend class ::grpc::internal::ClientCallbackReaderFactory;
template <class W>
friend class ::grpc::internal::ClientCallbackWriterFactory;
template <class InputMessage, class OutputMessage> template <class InputMessage, class OutputMessage>
friend class ::grpc::internal::BlockingUnaryCallImpl; friend class ::grpc::internal::BlockingUnaryCallImpl;
template <class InputMessage, class OutputMessage> template <class InputMessage, class OutputMessage>

@ -22,6 +22,7 @@
#include <functional> #include <functional>
#include <grpcpp/impl/codegen/call.h> #include <grpcpp/impl/codegen/call.h>
#include <grpcpp/impl/codegen/call_op_set.h>
#include <grpcpp/impl/codegen/callback_common.h> #include <grpcpp/impl/codegen/callback_common.h>
#include <grpcpp/impl/codegen/channel_interface.h> #include <grpcpp/impl/codegen/channel_interface.h>
#include <grpcpp/impl/codegen/config.h> #include <grpcpp/impl/codegen/config.h>
@ -88,6 +89,646 @@ class CallbackUnaryCallImpl {
call.PerformOps(ops); call.PerformOps(ops);
} }
}; };
} // namespace internal
namespace experimental {
// Forward declarations
template <class Request, class Response>
class ClientBidiReactor;
template <class Response>
class ClientReadReactor;
template <class Request>
class ClientWriteReactor;
// NOTE: The streaming objects are not actually implemented in the public API.
// These interfaces are provided for mocking only. Typical applications
// will interact exclusively with the reactors that they define.
template <class Request, class Response>
class ClientCallbackReaderWriter {
public:
virtual ~ClientCallbackReaderWriter() {}
virtual void StartCall() = 0;
virtual void Write(const Request* req, WriteOptions options) = 0;
virtual void WritesDone() = 0;
virtual void Read(Response* resp) = 0;
protected:
void BindReactor(ClientBidiReactor<Request, Response>* reactor) {
reactor->BindStream(this);
}
};
template <class Response>
class ClientCallbackReader {
public:
virtual ~ClientCallbackReader() {}
virtual void StartCall() = 0;
virtual void Read(Response* resp) = 0;
protected:
void BindReactor(ClientReadReactor<Response>* reactor) {
reactor->BindReader(this);
}
};
template <class Request>
class ClientCallbackWriter {
public:
virtual ~ClientCallbackWriter() {}
virtual void StartCall() = 0;
void Write(const Request* req) { Write(req, WriteOptions()); }
virtual void Write(const Request* req, WriteOptions options) = 0;
void WriteLast(const Request* req, WriteOptions options) {
Write(req, options.set_last_message());
}
virtual void WritesDone() = 0;
protected:
void BindReactor(ClientWriteReactor<Request>* reactor) {
reactor->BindWriter(this);
}
};
// The user must implement this reactor interface with reactions to each event
// type that gets called by the library. An empty reaction is provided by
// default
template <class Request, class Response>
class ClientBidiReactor {
public:
virtual ~ClientBidiReactor() {}
virtual void OnDone(const Status& s) {}
virtual void OnReadInitialMetadataDone(bool ok) {}
virtual void OnReadDone(bool ok) {}
virtual void OnWriteDone(bool ok) {}
virtual void OnWritesDoneDone(bool ok) {}
void StartCall() { stream_->StartCall(); }
void StartRead(Response* resp) { stream_->Read(resp); }
void StartWrite(const Request* req) { StartWrite(req, WriteOptions()); }
void StartWrite(const Request* req, WriteOptions options) {
stream_->Write(req, std::move(options));
}
void StartWriteLast(const Request* req, WriteOptions options) {
StartWrite(req, std::move(options.set_last_message()));
}
void StartWritesDone() { stream_->WritesDone(); }
private:
friend class ClientCallbackReaderWriter<Request, Response>;
void BindStream(ClientCallbackReaderWriter<Request, Response>* stream) {
stream_ = stream;
}
ClientCallbackReaderWriter<Request, Response>* stream_;
};
template <class Response>
class ClientReadReactor {
public:
virtual ~ClientReadReactor() {}
virtual void OnDone(const Status& s) {}
virtual void OnReadInitialMetadataDone(bool ok) {}
virtual void OnReadDone(bool ok) {}
void StartCall() { reader_->StartCall(); }
void StartRead(Response* resp) { reader_->Read(resp); }
private:
friend class ClientCallbackReader<Response>;
void BindReader(ClientCallbackReader<Response>* reader) { reader_ = reader; }
ClientCallbackReader<Response>* reader_;
};
template <class Request>
class ClientWriteReactor {
public:
virtual ~ClientWriteReactor() {}
virtual void OnDone(const Status& s) {}
virtual void OnReadInitialMetadataDone(bool ok) {}
virtual void OnWriteDone(bool ok) {}
virtual void OnWritesDoneDone(bool ok) {}
void StartCall() { writer_->StartCall(); }
void StartWrite(const Request* req) { StartWrite(req, WriteOptions()); }
void StartWrite(const Request* req, WriteOptions options) {
writer_->Write(req, std::move(options));
}
void StartWriteLast(const Request* req, WriteOptions options) {
StartWrite(req, std::move(options.set_last_message()));
}
void StartWritesDone() { writer_->WritesDone(); }
private:
friend class ClientCallbackWriter<Request>;
void BindWriter(ClientCallbackWriter<Request>* writer) { writer_ = writer; }
ClientCallbackWriter<Request>* writer_;
};
} // namespace experimental
namespace internal {
// Forward declare factory classes for friendship
template <class Request, class Response>
class ClientCallbackReaderWriterFactory;
template <class Response>
class ClientCallbackReaderFactory;
template <class Request>
class ClientCallbackWriterFactory;
template <class Request, class Response>
class ClientCallbackReaderWriterImpl
: public ::grpc::experimental::ClientCallbackReaderWriter<Request,
Response> {
public:
// always allocated against a call arena, no memory free required
static void operator delete(void* ptr, std::size_t size) {
assert(size == sizeof(ClientCallbackReaderWriterImpl));
}
// This operator should never be called as the memory should be freed as part
// of the arena destruction. It only exists to provide a matching operator
// delete to the operator new so that some compilers will not complain (see
// https://github.com/grpc/grpc/issues/11301) Note at the time of adding this
// there are no tests catching the compiler warning.
static void operator delete(void*, void*) { assert(0); }
void MaybeFinish() {
if (--callbacks_outstanding_ == 0) {
reactor_->OnDone(finish_status_);
auto* call = call_.call();
this->~ClientCallbackReaderWriterImpl();
g_core_codegen_interface->grpc_call_unref(call);
}
}
void StartCall() override {
// This call initiates two batches, plus any backlog, each with a callback
// 1. Send initial metadata (unless corked) + recv initial metadata
// 2. Any read backlog
// 3. Recv trailing metadata, on_completion callback
// 4. Any write backlog
started_ = true;
start_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnReadInitialMetadataDone(ok);
MaybeFinish();
},
&start_ops_);
if (!start_corked_) {
start_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
context_->initial_metadata_flags());
}
start_ops_.RecvInitialMetadata(context_);
start_ops_.set_core_cq_tag(&start_tag_);
call_.PerformOps(&start_ops_);
// Also set up the read and write tags so that they don't have to be set up
// each time
write_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnWriteDone(ok);
MaybeFinish();
},
&write_ops_);
write_ops_.set_core_cq_tag(&write_tag_);
read_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnReadDone(ok);
MaybeFinish();
},
&read_ops_);
read_ops_.set_core_cq_tag(&read_tag_);
if (read_ops_at_start_) {
call_.PerformOps(&read_ops_);
}
finish_tag_.Set(call_.call(), [this](bool ok) { MaybeFinish(); },
&finish_ops_);
finish_ops_.ClientRecvStatus(context_, &finish_status_);
finish_ops_.set_core_cq_tag(&finish_tag_);
call_.PerformOps(&finish_ops_);
if (write_ops_at_start_) {
call_.PerformOps(&write_ops_);
}
if (writes_done_ops_at_start_) {
call_.PerformOps(&writes_done_ops_);
}
}
void Read(Response* msg) override {
read_ops_.RecvMessage(msg);
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&read_ops_);
} else {
read_ops_at_start_ = true;
}
}
void Write(const Request* msg, WriteOptions options) override {
if (start_corked_) {
write_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
context_->initial_metadata_flags());
start_corked_ = false;
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(*msg).ok());
if (options.is_last_message()) {
options.set_buffer_hint();
write_ops_.ClientSendClose();
}
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&write_ops_);
} else {
write_ops_at_start_ = true;
}
}
void WritesDone() override {
if (start_corked_) {
writes_done_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
context_->initial_metadata_flags());
start_corked_ = false;
}
writes_done_ops_.ClientSendClose();
writes_done_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnWritesDoneDone(ok);
MaybeFinish();
},
&writes_done_ops_);
writes_done_ops_.set_core_cq_tag(&writes_done_tag_);
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&writes_done_ops_);
} else {
writes_done_ops_at_start_ = true;
}
}
private:
friend class ClientCallbackReaderWriterFactory<Request, Response>;
ClientCallbackReaderWriterImpl(
Call call, ClientContext* context,
::grpc::experimental::ClientBidiReactor<Request, Response>* reactor)
: context_(context),
call_(call),
reactor_(reactor),
start_corked_(context_->initial_metadata_corked_) {
this->BindReactor(reactor);
}
ClientContext* context_;
Call call_;
::grpc::experimental::ClientBidiReactor<Request, Response>* reactor_;
CallOpSet<CallOpSendInitialMetadata, CallOpRecvInitialMetadata> start_ops_;
CallbackWithSuccessTag start_tag_;
bool start_corked_;
CallOpSet<CallOpClientRecvStatus> finish_ops_;
CallbackWithSuccessTag finish_tag_;
Status finish_status_;
CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage, CallOpClientSendClose>
write_ops_;
CallbackWithSuccessTag write_tag_;
bool write_ops_at_start_{false};
CallOpSet<CallOpSendInitialMetadata, CallOpClientSendClose> writes_done_ops_;
CallbackWithSuccessTag writes_done_tag_;
bool writes_done_ops_at_start_{false};
CallOpSet<CallOpRecvMessage<Response>> read_ops_;
CallbackWithSuccessTag read_tag_;
bool read_ops_at_start_{false};
// Minimum of 2 outstanding callbacks to pre-register for start and finish
std::atomic_int callbacks_outstanding_{2};
bool started_{false};
};
template <class Request, class Response>
class ClientCallbackReaderWriterFactory {
public:
static void Create(
ChannelInterface* channel, const ::grpc::internal::RpcMethod& method,
ClientContext* context,
::grpc::experimental::ClientBidiReactor<Request, Response>* reactor) {
Call call = channel->CreateCall(method, context, channel->CallbackCQ());
g_core_codegen_interface->grpc_call_ref(call.call());
new (g_core_codegen_interface->grpc_call_arena_alloc(
call.call(), sizeof(ClientCallbackReaderWriterImpl<Request, Response>)))
ClientCallbackReaderWriterImpl<Request, Response>(call, context,
reactor);
}
};
template <class Response>
class ClientCallbackReaderImpl
: public ::grpc::experimental::ClientCallbackReader<Response> {
public:
// always allocated against a call arena, no memory free required
static void operator delete(void* ptr, std::size_t size) {
assert(size == sizeof(ClientCallbackReaderImpl));
}
// This operator should never be called as the memory should be freed as part
// of the arena destruction. It only exists to provide a matching operator
// delete to the operator new so that some compilers will not complain (see
// https://github.com/grpc/grpc/issues/11301) Note at the time of adding this
// there are no tests catching the compiler warning.
static void operator delete(void*, void*) { assert(0); }
void MaybeFinish() {
if (--callbacks_outstanding_ == 0) {
reactor_->OnDone(finish_status_);
auto* call = call_.call();
this->~ClientCallbackReaderImpl();
g_core_codegen_interface->grpc_call_unref(call);
}
}
void StartCall() override {
// This call initiates two batches, plus any backlog, each with a callback
// 1. Send initial metadata (unless corked) + recv initial metadata
// 2. Any backlog
// 3. Recv trailing metadata, on_completion callback
started_ = true;
start_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnReadInitialMetadataDone(ok);
MaybeFinish();
},
&start_ops_);
start_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
context_->initial_metadata_flags());
start_ops_.RecvInitialMetadata(context_);
start_ops_.set_core_cq_tag(&start_tag_);
call_.PerformOps(&start_ops_);
// Also set up the read tag so it doesn't have to be set up each time
read_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnReadDone(ok);
MaybeFinish();
},
&read_ops_);
read_ops_.set_core_cq_tag(&read_tag_);
if (read_ops_at_start_) {
call_.PerformOps(&read_ops_);
}
finish_tag_.Set(call_.call(), [this](bool ok) { MaybeFinish(); },
&finish_ops_);
finish_ops_.ClientRecvStatus(context_, &finish_status_);
finish_ops_.set_core_cq_tag(&finish_tag_);
call_.PerformOps(&finish_ops_);
}
void Read(Response* msg) override {
read_ops_.RecvMessage(msg);
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&read_ops_);
} else {
read_ops_at_start_ = true;
}
}
private:
friend class ClientCallbackReaderFactory<Response>;
template <class Request>
ClientCallbackReaderImpl(
Call call, ClientContext* context, Request* request,
::grpc::experimental::ClientReadReactor<Response>* reactor)
: context_(context), call_(call), reactor_(reactor) {
this->BindReactor(reactor);
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(start_ops_.SendMessage(*request).ok());
start_ops_.ClientSendClose();
}
ClientContext* context_;
Call call_;
::grpc::experimental::ClientReadReactor<Response>* reactor_;
CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage, CallOpClientSendClose,
CallOpRecvInitialMetadata>
start_ops_;
CallbackWithSuccessTag start_tag_;
CallOpSet<CallOpClientRecvStatus> finish_ops_;
CallbackWithSuccessTag finish_tag_;
Status finish_status_;
CallOpSet<CallOpRecvMessage<Response>> read_ops_;
CallbackWithSuccessTag read_tag_;
bool read_ops_at_start_{false};
// Minimum of 2 outstanding callbacks to pre-register for start and finish
std::atomic_int callbacks_outstanding_{2};
bool started_{false};
};
template <class Response>
class ClientCallbackReaderFactory {
public:
template <class Request>
static void Create(
ChannelInterface* channel, const ::grpc::internal::RpcMethod& method,
ClientContext* context, const Request* request,
::grpc::experimental::ClientReadReactor<Response>* reactor) {
Call call = channel->CreateCall(method, context, channel->CallbackCQ());
g_core_codegen_interface->grpc_call_ref(call.call());
new (g_core_codegen_interface->grpc_call_arena_alloc(
call.call(), sizeof(ClientCallbackReaderImpl<Response>)))
ClientCallbackReaderImpl<Response>(call, context, request, reactor);
}
};
template <class Request>
class ClientCallbackWriterImpl
: public ::grpc::experimental::ClientCallbackWriter<Request> {
public:
// always allocated against a call arena, no memory free required
static void operator delete(void* ptr, std::size_t size) {
assert(size == sizeof(ClientCallbackWriterImpl));
}
// This operator should never be called as the memory should be freed as part
// of the arena destruction. It only exists to provide a matching operator
// delete to the operator new so that some compilers will not complain (see
// https://github.com/grpc/grpc/issues/11301) Note at the time of adding this
// there are no tests catching the compiler warning.
static void operator delete(void*, void*) { assert(0); }
void MaybeFinish() {
if (--callbacks_outstanding_ == 0) {
reactor_->OnDone(finish_status_);
auto* call = call_.call();
this->~ClientCallbackWriterImpl();
g_core_codegen_interface->grpc_call_unref(call);
}
}
void StartCall() override {
// This call initiates two batches, plus any backlog, each with a callback
// 1. Send initial metadata (unless corked) + recv initial metadata
// 2. Recv trailing metadata, on_completion callback
// 3. Any backlog
started_ = true;
start_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnReadInitialMetadataDone(ok);
MaybeFinish();
},
&start_ops_);
if (!start_corked_) {
start_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
context_->initial_metadata_flags());
}
start_ops_.RecvInitialMetadata(context_);
start_ops_.set_core_cq_tag(&start_tag_);
call_.PerformOps(&start_ops_);
// Also set up the read and write tags so that they don't have to be set up
// each time
write_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnWriteDone(ok);
MaybeFinish();
},
&write_ops_);
write_ops_.set_core_cq_tag(&write_tag_);
finish_tag_.Set(call_.call(), [this](bool ok) { MaybeFinish(); },
&finish_ops_);
finish_ops_.ClientRecvStatus(context_, &finish_status_);
finish_ops_.set_core_cq_tag(&finish_tag_);
call_.PerformOps(&finish_ops_);
if (write_ops_at_start_) {
call_.PerformOps(&write_ops_);
}
if (writes_done_ops_at_start_) {
call_.PerformOps(&writes_done_ops_);
}
}
void Write(const Request* msg, WriteOptions options) override {
if (start_corked_) {
write_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
context_->initial_metadata_flags());
start_corked_ = false;
}
// TODO(vjpai): don't assert
GPR_CODEGEN_ASSERT(write_ops_.SendMessage(*msg).ok());
if (options.is_last_message()) {
options.set_buffer_hint();
write_ops_.ClientSendClose();
}
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&write_ops_);
} else {
write_ops_at_start_ = true;
}
}
void WritesDone() override {
if (start_corked_) {
writes_done_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
context_->initial_metadata_flags());
start_corked_ = false;
}
writes_done_ops_.ClientSendClose();
writes_done_tag_.Set(call_.call(),
[this](bool ok) {
reactor_->OnWritesDoneDone(ok);
MaybeFinish();
},
&writes_done_ops_);
writes_done_ops_.set_core_cq_tag(&writes_done_tag_);
callbacks_outstanding_++;
if (started_) {
call_.PerformOps(&writes_done_ops_);
} else {
writes_done_ops_at_start_ = true;
}
}
private:
friend class ClientCallbackWriterFactory<Request>;
template <class Response>
ClientCallbackWriterImpl(
Call call, ClientContext* context, Response* response,
::grpc::experimental::ClientWriteReactor<Request>* reactor)
: context_(context),
call_(call),
reactor_(reactor),
start_corked_(context_->initial_metadata_corked_) {
this->BindReactor(reactor);
finish_ops_.RecvMessage(response);
finish_ops_.AllowNoMessage();
}
ClientContext* context_;
Call call_;
::grpc::experimental::ClientWriteReactor<Request>* reactor_;
CallOpSet<CallOpSendInitialMetadata, CallOpRecvInitialMetadata> start_ops_;
CallbackWithSuccessTag start_tag_;
bool start_corked_;
CallOpSet<CallOpGenericRecvMessage, CallOpClientRecvStatus> finish_ops_;
CallbackWithSuccessTag finish_tag_;
Status finish_status_;
CallOpSet<CallOpSendInitialMetadata, CallOpSendMessage, CallOpClientSendClose>
write_ops_;
CallbackWithSuccessTag write_tag_;
bool write_ops_at_start_{false};
CallOpSet<CallOpSendInitialMetadata, CallOpClientSendClose> writes_done_ops_;
CallbackWithSuccessTag writes_done_tag_;
bool writes_done_ops_at_start_{false};
// Minimum of 2 outstanding callbacks to pre-register for start and finish
std::atomic_int callbacks_outstanding_{2};
bool started_{false};
};
template <class Request>
class ClientCallbackWriterFactory {
public:
template <class Response>
static void Create(
ChannelInterface* channel, const ::grpc::internal::RpcMethod& method,
ClientContext* context, Response* response,
::grpc::experimental::ClientWriteReactor<Request>* reactor) {
Call call = channel->CreateCall(method, context, channel->CallbackCQ());
g_core_codegen_interface->grpc_call_ref(call.call());
new (g_core_codegen_interface->grpc_call_arena_alloc(
call.call(), sizeof(ClientCallbackWriterImpl<Request>)))
ClientCallbackWriterImpl<Request>(call, context, response, reactor);
}
};
} // namespace internal } // namespace internal
} // namespace grpc } // namespace grpc

@ -71,6 +71,12 @@ template <class InputMessage, class OutputMessage>
class BlockingUnaryCallImpl; class BlockingUnaryCallImpl;
template <class InputMessage, class OutputMessage> template <class InputMessage, class OutputMessage>
class CallbackUnaryCallImpl; class CallbackUnaryCallImpl;
template <class Request, class Response>
class ClientCallbackReaderWriterImpl;
template <class Response>
class ClientCallbackReaderImpl;
template <class Request>
class ClientCallbackWriterImpl;
} // namespace internal } // namespace internal
template <class R> template <class R>
@ -394,6 +400,12 @@ class ClientContext {
friend class ::grpc::internal::BlockingUnaryCallImpl; friend class ::grpc::internal::BlockingUnaryCallImpl;
template <class InputMessage, class OutputMessage> template <class InputMessage, class OutputMessage>
friend class ::grpc::internal::CallbackUnaryCallImpl; friend class ::grpc::internal::CallbackUnaryCallImpl;
template <class Request, class Response>
friend class ::grpc::internal::ClientCallbackReaderWriterImpl;
template <class Response>
friend class ::grpc::internal::ClientCallbackReaderImpl;
template <class Request>
friend class ::grpc::internal::ClientCallbackWriterImpl;
// Used by friend class CallOpClientRecvStatus // Used by friend class CallOpClientRecvStatus
void set_debug_error_string(const grpc::string& debug_error_string) { void set_debug_error_string(const grpc::string& debug_error_string) {

@ -132,6 +132,7 @@ grpc::string GetHeaderIncludes(grpc_generator::File* file,
"grpcpp/impl/codegen/async_generic_service.h", "grpcpp/impl/codegen/async_generic_service.h",
"grpcpp/impl/codegen/async_stream.h", "grpcpp/impl/codegen/async_stream.h",
"grpcpp/impl/codegen/async_unary_call.h", "grpcpp/impl/codegen/async_unary_call.h",
"grpcpp/impl/codegen/client_callback.h",
"grpcpp/impl/codegen/method_handler_impl.h", "grpcpp/impl/codegen/method_handler_impl.h",
"grpcpp/impl/codegen/proto_utils.h", "grpcpp/impl/codegen/proto_utils.h",
"grpcpp/impl/codegen/rpc_method.h", "grpcpp/impl/codegen/rpc_method.h",
@ -580,11 +581,22 @@ void PrintHeaderClientMethodCallbackInterfaces(
"const $Request$* request, $Response$* response, " "const $Request$* request, $Response$* response, "
"std::function<void(::grpc::Status)>) = 0;\n"); "std::function<void(::grpc::Status)>) = 0;\n");
} else if (ClientOnlyStreaming(method)) { } else if (ClientOnlyStreaming(method)) {
// TODO(vjpai): Add support for client-side streaming printer->Print(*vars,
"virtual void $Method$(::grpc::ClientContext* context, "
"$Response$* response, "
"::grpc::experimental::ClientWriteReactor< $Request$>* "
"reactor) = 0;\n");
} else if (ServerOnlyStreaming(method)) { } else if (ServerOnlyStreaming(method)) {
// TODO(vjpai): Add support for server-side streaming printer->Print(*vars,
"virtual void $Method$(::grpc::ClientContext* context, "
"$Request$* request, "
"::grpc::experimental::ClientReadReactor< $Response$>* "
"reactor) = 0;\n");
} else if (method->BidiStreaming()) { } else if (method->BidiStreaming()) {
// TODO(vjpai): Add support for bidi streaming printer->Print(*vars,
"virtual void $Method$(::grpc::ClientContext* context, "
"::grpc::experimental::ClientBidiReactor< "
"$Request$,$Response$>* reactor) = 0;\n");
} }
} }
@ -631,11 +643,23 @@ void PrintHeaderClientMethodCallback(grpc_generator::Printer* printer,
"const $Request$* request, $Response$* response, " "const $Request$* request, $Response$* response, "
"std::function<void(::grpc::Status)>) override;\n"); "std::function<void(::grpc::Status)>) override;\n");
} else if (ClientOnlyStreaming(method)) { } else if (ClientOnlyStreaming(method)) {
// TODO(vjpai): Add support for client-side streaming printer->Print(*vars,
"void $Method$(::grpc::ClientContext* context, "
"$Response$* response, "
"::grpc::experimental::ClientWriteReactor< $Request$>* "
"reactor) override;\n");
} else if (ServerOnlyStreaming(method)) { } else if (ServerOnlyStreaming(method)) {
// TODO(vjpai): Add support for server-side streaming printer->Print(*vars,
"void $Method$(::grpc::ClientContext* context, "
"$Request$* request, "
"::grpc::experimental::ClientReadReactor< $Response$>* "
"reactor) override;\n");
} else if (method->BidiStreaming()) { } else if (method->BidiStreaming()) {
// TODO(vjpai): Add support for bidi streaming printer->Print(*vars,
"void $Method$(::grpc::ClientContext* context, "
"::grpc::experimental::ClientBidiReactor< "
"$Request$,$Response$>* reactor) override;\n");
} }
} }
@ -1607,7 +1631,19 @@ void PrintSourceClientMethod(grpc_generator::Printer* printer,
"context, response);\n" "context, response);\n"
"}\n\n"); "}\n\n");
// TODO(vjpai): Add callback version printer->Print(
*vars,
"void $ns$$Service$::"
"Stub::experimental_async::$Method$(::grpc::ClientContext* context, "
"$Response$* response, "
"::grpc::experimental::ClientWriteReactor< $Request$>* reactor) {\n");
printer->Print(*vars,
" ::grpc::internal::ClientCallbackWriterFactory< "
"$Request$>::Create("
"stub_->channel_.get(), "
"stub_->rpcmethod_$Method$_, "
"context, response, reactor);\n"
"}\n\n");
for (auto async_prefix : async_prefixes) { for (auto async_prefix : async_prefixes) {
(*vars)["AsyncPrefix"] = async_prefix.prefix; (*vars)["AsyncPrefix"] = async_prefix.prefix;
@ -1641,7 +1677,19 @@ void PrintSourceClientMethod(grpc_generator::Printer* printer,
"context, request);\n" "context, request);\n"
"}\n\n"); "}\n\n");
// TODO(vjpai): Add callback version printer->Print(
*vars,
"void $ns$$Service$::Stub::experimental_async::$Method$(::grpc::"
"ClientContext* context, "
"$Request$* request, "
"::grpc::experimental::ClientReadReactor< $Response$>* reactor) {\n");
printer->Print(*vars,
" ::grpc::internal::ClientCallbackReaderFactory< "
"$Response$>::Create("
"stub_->channel_.get(), "
"stub_->rpcmethod_$Method$_, "
"context, request, reactor);\n"
"}\n\n");
for (auto async_prefix : async_prefixes) { for (auto async_prefix : async_prefixes) {
(*vars)["AsyncPrefix"] = async_prefix.prefix; (*vars)["AsyncPrefix"] = async_prefix.prefix;
@ -1675,7 +1723,19 @@ void PrintSourceClientMethod(grpc_generator::Printer* printer,
"context);\n" "context);\n"
"}\n\n"); "}\n\n");
// TODO(vjpai): Add callback version printer->Print(
*vars,
"void $ns$$Service$::Stub::experimental_async::$Method$(::grpc::"
"ClientContext* context, "
"::grpc::experimental::ClientBidiReactor< $Request$,$Response$>* "
"reactor) {\n");
printer->Print(*vars,
" ::grpc::internal::ClientCallbackReaderWriterFactory< "
"$Request$,$Response$>::Create("
"stub_->channel_.get(), "
"stub_->rpcmethod_$Method$_, "
"context, reactor);\n"
"}\n\n");
for (auto async_prefix : async_prefixes) { for (auto async_prefix : async_prefixes) {
(*vars)["AsyncPrefix"] = async_prefix.prefix; (*vars)["AsyncPrefix"] = async_prefix.prefix;

@ -72,4 +72,13 @@ void GenericStub::experimental_type::UnaryCall(
context, request, response, std::move(on_completion)); context, request, response, std::move(on_completion));
} }
void GenericStub::experimental_type::PrepareBidiStreamingCall(
ClientContext* context, const grpc::string& method,
experimental::ClientBidiReactor<ByteBuffer, ByteBuffer>* reactor) {
internal::ClientCallbackReaderWriterFactory<ByteBuffer, ByteBuffer>::Create(
stub_->channel_.get(),
internal::RpcMethod(method.c_str(), internal::RpcMethod::BIDI_STREAMING),
context, reactor);
}
} // namespace grpc } // namespace grpc

@ -30,6 +30,7 @@
#include <grpcpp/impl/codegen/async_generic_service.h> #include <grpcpp/impl/codegen/async_generic_service.h>
#include <grpcpp/impl/codegen/async_stream.h> #include <grpcpp/impl/codegen/async_stream.h>
#include <grpcpp/impl/codegen/async_unary_call.h> #include <grpcpp/impl/codegen/async_unary_call.h>
#include <grpcpp/impl/codegen/client_callback.h>
#include <grpcpp/impl/codegen/method_handler_impl.h> #include <grpcpp/impl/codegen/method_handler_impl.h>
#include <grpcpp/impl/codegen/proto_utils.h> #include <grpcpp/impl/codegen/proto_utils.h>
#include <grpcpp/impl/codegen/rpc_method.h> #include <grpcpp/impl/codegen/rpc_method.h>
@ -117,10 +118,13 @@ class ServiceA final {
// //
// Method A2 leading comment 1 // Method A2 leading comment 1
// Method A2 leading comment 2 // Method A2 leading comment 2
virtual void MethodA2(::grpc::ClientContext* context, ::grpc::testing::Response* response, ::grpc::experimental::ClientWriteReactor< ::grpc::testing::Request>* reactor) = 0;
// MethodA2 trailing comment 1 // MethodA2 trailing comment 1
// Method A3 leading comment 1 // Method A3 leading comment 1
virtual void MethodA3(::grpc::ClientContext* context, ::grpc::testing::Request* request, ::grpc::experimental::ClientReadReactor< ::grpc::testing::Response>* reactor) = 0;
// Method A3 trailing comment 1 // Method A3 trailing comment 1
// Method A4 leading comment 1 // Method A4 leading comment 1
virtual void MethodA4(::grpc::ClientContext* context, ::grpc::experimental::ClientBidiReactor< ::grpc::testing::Request,::grpc::testing::Response>* reactor) = 0;
// Method A4 trailing comment 1 // Method A4 trailing comment 1
}; };
virtual class experimental_async_interface* experimental_async() { return nullptr; } virtual class experimental_async_interface* experimental_async() { return nullptr; }
@ -178,6 +182,9 @@ class ServiceA final {
public StubInterface::experimental_async_interface { public StubInterface::experimental_async_interface {
public: public:
void MethodA1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) override; void MethodA1(::grpc::ClientContext* context, const ::grpc::testing::Request* request, ::grpc::testing::Response* response, std::function<void(::grpc::Status)>) override;
void MethodA2(::grpc::ClientContext* context, ::grpc::testing::Response* response, ::grpc::experimental::ClientWriteReactor< ::grpc::testing::Request>* reactor) override;
void MethodA3(::grpc::ClientContext* context, ::grpc::testing::Request* request, ::grpc::experimental::ClientReadReactor< ::grpc::testing::Response>* reactor) override;
void MethodA4(::grpc::ClientContext* context, ::grpc::experimental::ClientBidiReactor< ::grpc::testing::Request,::grpc::testing::Response>* reactor) override;
private: private:
friend class Stub; friend class Stub;
explicit experimental_async(Stub* stub): stub_(stub) { } explicit experimental_async(Stub* stub): stub_(stub) { }

@ -182,6 +182,55 @@ class ClientCallbackEnd2endTest
} }
} }
void SendGenericEchoAsBidi(int num_rpcs) {
const grpc::string kMethodName("/grpc.testing.EchoTestService/Echo");
grpc::string test_string("");
for (int i = 0; i < num_rpcs; i++) {
test_string += "Hello world. ";
class Client : public grpc::experimental::ClientBidiReactor<ByteBuffer,
ByteBuffer> {
public:
Client(ClientCallbackEnd2endTest* test, const grpc::string& method_name,
const grpc::string& test_str) {
test->generic_stub_->experimental().PrepareBidiStreamingCall(
&cli_ctx_, method_name, this);
request_.set_message(test_str);
send_buf_ = SerializeToByteBuffer(&request_);
StartWrite(send_buf_.get());
StartRead(&recv_buf_);
StartCall();
}
void OnWriteDone(bool ok) override { StartWritesDone(); }
void OnReadDone(bool ok) override {
EchoResponse response;
EXPECT_TRUE(ParseFromByteBuffer(&recv_buf_, &response));
EXPECT_EQ(request_.message(), response.message());
};
void OnDone(const Status& s) override {
EXPECT_TRUE(s.ok());
std::unique_lock<std::mutex> l(mu_);
done_ = true;
cv_.notify_one();
}
void Await() {
std::unique_lock<std::mutex> l(mu_);
while (!done_) {
cv_.wait(l);
}
}
EchoRequest request_;
std::unique_ptr<ByteBuffer> send_buf_;
ByteBuffer recv_buf_;
ClientContext cli_ctx_;
std::mutex mu_;
std::condition_variable cv_;
bool done_ = false;
} rpc{this, kMethodName, test_string};
rpc.Await();
}
}
bool is_server_started_; bool is_server_started_;
std::shared_ptr<Channel> channel_; std::shared_ptr<Channel> channel_;
std::unique_ptr<grpc::testing::EchoTestService::Stub> stub_; std::unique_ptr<grpc::testing::EchoTestService::Stub> stub_;
@ -211,6 +260,11 @@ TEST_P(ClientCallbackEnd2endTest, SequentialGenericRpcs) {
SendRpcsGeneric(10, false); SendRpcsGeneric(10, false);
} }
TEST_P(ClientCallbackEnd2endTest, SequentialGenericRpcsAsBidi) {
ResetStub();
SendGenericEchoAsBidi(10);
}
#if GRPC_ALLOW_EXCEPTIONS #if GRPC_ALLOW_EXCEPTIONS
TEST_P(ClientCallbackEnd2endTest, ExceptingRpc) { TEST_P(ClientCallbackEnd2endTest, ExceptingRpc) {
ResetStub(); ResetStub();
@ -267,6 +321,170 @@ TEST_P(ClientCallbackEnd2endTest, CancelRpcBeforeStart) {
} }
} }
TEST_P(ClientCallbackEnd2endTest, RequestStream) {
// TODO(vjpai): test with callback server once supported
if (GetParam().callback_server) {
return;
}
ResetStub();
class Client : public grpc::experimental::ClientWriteReactor<EchoRequest> {
public:
explicit Client(grpc::testing::EchoTestService::Stub* stub) {
context_.set_initial_metadata_corked(true);
stub->experimental_async()->RequestStream(&context_, &response_, this);
StartCall();
request_.set_message("Hello server.");
StartWrite(&request_);
}
void OnWriteDone(bool ok) override {
writes_left_--;
if (writes_left_ > 1) {
StartWrite(&request_);
} else if (writes_left_ == 1) {
StartWriteLast(&request_, WriteOptions());
}
}
void OnDone(const Status& s) override {
EXPECT_TRUE(s.ok());
EXPECT_EQ(response_.message(), "Hello server.Hello server.Hello server.");
std::unique_lock<std::mutex> l(mu_);
done_ = true;
cv_.notify_one();
}
void Await() {
std::unique_lock<std::mutex> l(mu_);
while (!done_) {
cv_.wait(l);
}
}
private:
EchoRequest request_;
EchoResponse response_;
ClientContext context_;
int writes_left_{3};
std::mutex mu_;
std::condition_variable cv_;
bool done_ = false;
} test{stub_.get()};
test.Await();
}
TEST_P(ClientCallbackEnd2endTest, ResponseStream) {
// TODO(vjpai): test with callback server once supported
if (GetParam().callback_server) {
return;
}
ResetStub();
class Client : public grpc::experimental::ClientReadReactor<EchoResponse> {
public:
explicit Client(grpc::testing::EchoTestService::Stub* stub) {
request_.set_message("Hello client ");
stub->experimental_async()->ResponseStream(&context_, &request_, this);
StartCall();
StartRead(&response_);
}
void OnReadDone(bool ok) override {
if (!ok) {
EXPECT_EQ(reads_complete_, kServerDefaultResponseStreamsToSend);
} else {
EXPECT_LE(reads_complete_, kServerDefaultResponseStreamsToSend);
EXPECT_EQ(response_.message(),
request_.message() + grpc::to_string(reads_complete_));
reads_complete_++;
StartRead(&response_);
}
}
void OnDone(const Status& s) override {
EXPECT_TRUE(s.ok());
std::unique_lock<std::mutex> l(mu_);
done_ = true;
cv_.notify_one();
}
void Await() {
std::unique_lock<std::mutex> l(mu_);
while (!done_) {
cv_.wait(l);
}
}
private:
EchoRequest request_;
EchoResponse response_;
ClientContext context_;
int reads_complete_{0};
std::mutex mu_;
std::condition_variable cv_;
bool done_ = false;
} test{stub_.get()};
test.Await();
}
TEST_P(ClientCallbackEnd2endTest, BidiStream) {
// TODO(vjpai): test with callback server once supported
if (GetParam().callback_server) {
return;
}
ResetStub();
class Client : public grpc::experimental::ClientBidiReactor<EchoRequest,
EchoResponse> {
public:
explicit Client(grpc::testing::EchoTestService::Stub* stub) {
request_.set_message("Hello fren ");
stub->experimental_async()->BidiStream(&context_, this);
StartCall();
StartRead(&response_);
StartWrite(&request_);
}
void OnReadDone(bool ok) override {
if (!ok) {
EXPECT_EQ(reads_complete_, kServerDefaultResponseStreamsToSend);
} else {
EXPECT_LE(reads_complete_, kServerDefaultResponseStreamsToSend);
EXPECT_EQ(response_.message(), request_.message());
reads_complete_++;
StartRead(&response_);
}
}
void OnWriteDone(bool ok) override {
EXPECT_TRUE(ok);
if (++writes_complete_ == kServerDefaultResponseStreamsToSend) {
StartWritesDone();
} else {
StartWrite(&request_);
}
}
void OnDone(const Status& s) override {
EXPECT_TRUE(s.ok());
std::unique_lock<std::mutex> l(mu_);
done_ = true;
cv_.notify_one();
}
void Await() {
std::unique_lock<std::mutex> l(mu_);
while (!done_) {
cv_.wait(l);
}
}
private:
EchoRequest request_;
EchoResponse response_;
ClientContext context_;
int reads_complete_{0};
int writes_complete_{0};
std::mutex mu_;
std::condition_variable cv_;
bool done_ = false;
} test{stub_.get()};
test.Await();
}
TestScenario scenarios[] = {TestScenario{false}, TestScenario{true}}; TestScenario scenarios[] = {TestScenario{false}, TestScenario{true}};
INSTANTIATE_TEST_CASE_P(ClientCallbackEnd2endTest, ClientCallbackEnd2endTest, INSTANTIATE_TEST_CASE_P(ClientCallbackEnd2endTest, ClientCallbackEnd2endTest,

@ -223,6 +223,7 @@ void CallbackTestServiceImpl::EchoNonDelayed(
return; return;
} }
gpr_log(GPR_DEBUG, "Request message was %s", request->message().c_str());
response->set_message(request->message()); response->set_message(request->message());
MaybeEchoDeadline(context, request, response); MaybeEchoDeadline(context, request, response);
if (host_) { if (host_) {

Loading…
Cancel
Save