-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using asynchronous gRPC server and client on same context #81
Comments
Hi, thanks for the detailed issue description. You can run client and server on the same GrpcContext like you are doing. In fact, most asio-grpc tests do the same. Note that But the actual problem lies in this line: auto result = co_await(agrpc::read(reader_writer, cmd) ||
alarm.wait(std::chrono::system_clock::now() + std::chrono::milliseconds(duration_msec))); GRPC does not support cancellation of individual reads/writes and therefore neither does asio-grpc. I am currently working on a new API for clients and server ( In the meantime you can use this experimental and not-quite-bug-free alternative based on // bring in the server shutdown functionality
example::ServerShutdown server_shutdown{*server, grpc_context};
// install a request handler for the bidirectional streaming endpoint
agrpc::repeatedly_request(&example::v1::Example::AsyncService::RequestBidirectionalStreaming, service,
asio::bind_executor(grpc_context,
[&](::grpc::ServerContext &server, ::grpc::ServerAsyncReaderWriter<example::v1::Response, example::v1::Request> &reader_writer) -> asio::awaitable<void>
{
std::cout << "server: started request\n";
// set up an alarm that is used to pace our responses to the client
agrpc::Alarm alarm(grpc_context);
int64_t duration_msec = 1000;
agrpc::GrpcStream stream{grpc_context};
example::v1::Request cmd;
stream.initiate(agrpc::read, reader_writer, cmd);
while (true)
{
using namespace asio::experimental::awaitable_operators;
// wait for the first of two events to happen:
//
// 1. we receive a new streaming message from the client
// 2. the timer expires
//
// when the timer expires, we send a message to the client.
std::cout << "server: reading/waiting\n";
auto result = co_await(stream.next() ||
alarm.wait(std::chrono::system_clock::now() + std::chrono::milliseconds(duration_msec)));
if (result.index() == 0)
{
if (std::get<0>(result))
{
std::cout << "server: got streaming message\n";
stream.initiate(agrpc::read, reader_writer, cmd);
}
else
{
std::cout << "server: read failed\n";
break;
}
}
else if (server_shutdown.is_shutdown)
{
break;
}
else
{
std::cout << "server: alarm expired\n";
example::v1::Response resp;
if (!co_await agrpc::write(reader_writer, resp))
{
// client disconnected
co_return;
}
}
}
co_await agrpc::finish(reader_writer, ::grpc::Status::OK);
}
)); |
Thanks for the detailed answer! I will give that a try. A followup question: can you elaborate on how the approach is "not-quite-bug-free"? Just trying to determine whether there are any showstopping reasons why I wouldn't want to do this. |
I believe the main issue is that the completion of a read is not sticky within GrpcStream. If cancellation and successful completion of the read occur at the same time than cancellation takes precedence. The next call to |
That makes sense. If you know a reasonable fix for this, then I would welcome it; I think the |
I re-added a previously flaky example as a unit test but I couldn't find any issues with it. Maybe it is just something specifically wrong with that example and not with GrpcStream/CancelSafe. One thing that is missing from my code snippet above is the call to co_await stream.cleanup();
co_await agrpc::finish(reader_writer, ::grpc::Status::OK); which either waits for a previously initiated read to complete and completes immediately if none is pending. This is helpful because you must await all initiated reads before GrpcStream is being destructed. |
Thanks for the info. I have been working on what the process looks like for shutting down the server when a bidirectional streaming connection is open. I extended the example above to implement the shutdown logic using a I tried adding the call to
I think this is what you were getting at when you said before that the read cannot be cancelled? Is there a clean way around this, so that the server can shut down without potentially being held hostage by clients that aren't aware of the shutdown? I would be fine with the client seeing an error on the RPC in this case, but I'm not sure what the best practice is for this sort of thing. Alternatively, is it permissible to move the |
I have tested it and on Windows it seems to be fine to have an uncompleted Finish:
Read:
It is true that the client-side Alternatively you can also call I have created a complete example of the code here: https://github.com/Tradias/example-vcpkg-grpc/tree/asio-grpc-81. Might make it easier to refer to. Did you know that asio also has a promise implementation https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio/reference/experimental__promise.html ? I don't think it can be shared however. |
Using |
Great, don't hesitate to open another issue if you have more questions! |
I am developing a gRPC service interface for the first time. I am reasonably familiar with
asio
, but definitely not expert. Getting a basic server framework going from the examples is straightforward. What I'm trying to do now is put together a simple testing framework that will allow me to write unit tests for my service. It would integrate most easily into my existing tests if I could make it single-process instead of requiring a server and client to be launched separately, so I was trying to write a simple test wrapper that would instantiate the server, then perform asynchronous client requests to test the server's behavior.With that said, I've edited one of the example programs to try to demonstrate what I want to do:
My intended behavior when running the program would be:
BidirectionalStreaming
RPC.Request
streaming message to the server.Request
.Response
streaming messages periodically to the client (indefinitely in this simple example)SIGINT
to the process and theServerShutdown
will shut down the server, the next read/write on the RPC fail for the server/client, and the coroutines exit cleanly.I'm not seeing what I expected, likely because I'm missing something fundamental about how this should work. When I run the above example program, I get:
And nothing else. Specifically, the coroutine handling the
BidirectionalStreaming
RPC on the server side seems to get stuck in theco_await
that should complete when either a new message is received or the alarm expires. The alarm expiration doesn't ever seem to fire.Furthermore, if I try to send
SIGINT
orSIGTERM
to the process, it does not respond. If I take theco_spawn
for the client coroutine out, then I can at least use the signals to shut down the server cleanly.Should this type of strategy with with asio-grpc?
The text was updated successfully, but these errors were encountered: