Skip to content
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

"Pure virtual method called" exception #20

Closed
eduardokussler opened this issue May 6, 2022 · 5 comments
Closed

"Pure virtual method called" exception #20

eduardokussler opened this issue May 6, 2022 · 5 comments

Comments

@eduardokussler
Copy link

First, let me be clear that i don't know much about the boost library, so maybe my mistake is in that regard. The issue I'm facing is: I have a server-streaming service that needs to send data every x seconds to a client. There could be multiple requests from the same client, as long as the parameters of the request are different as well as multiple clients connected at the same time. To do that, what I did was start a repeatedly_request as shown on issue #14 :

boost::asio::system_context ctx;
    auto guard = boost::asio::make_work_guard(ctx);
    
    agrpc::repeatedly_request(&MarketDataAlert::AsyncService::Requestsubscribe, service,
                              boost::asio::bind_executor(grpc_context,
                                                  [&]<class T>(agrpc::RepeatedlyRequestContext<T>&& context)
                                                  {
                                                      
                                                      boost::asio::co_spawn(
                                                              ctx,
                                                              [&, context = std::move(context)]()
                                                              {
                                                                  auto args = context.args();
                                                                  return std::invoke(handle_request, std::get<0>(args), std::get<1>(args), std::get<2>(args), grpc_context);
                                                              },
                                                              boost::asio::detached);
                                                  }));

On the handle_request coroutine, if the request is valid, I tried 2 different variations. The first is this:

co_await processRequest(server_context,
                                request,
                                writer, instrumentId, grpc_context);

Which kinda works as I expected except I can only handle one request at a time, which makes it unviable for my use case.
The second attempt was to co_spawn processRequest, as such:

boost::asio::system_context ctx;
        boost::asio::co_spawn(
                ctx,
                [&]() -> boost::asio::awaitable<void>
                {
                    auto guard = boost::asio::make_work_guard(ctx);
                    co_await processRequest(server_context,
                                   request,
                                   writer, instrumentId, grpc_context);
                },
                boost::asio::detached);

But now, upon calling agrpc::write, I have a "pure virtual method called" exception. As stated on the documentation, since I'm using another context instead of the agrpc::GrpcContext, I always use bind_executor(grpc_context, asio::awaitable).
The processRequest method has the following structure:

while(request_ok) {
		// do some work
		co_await fill_response(server_context, request, response, instrumentId);
 
		// do some work
                request_ok = co_await agrpc::write(writer, response,
                                                              boost::asio::bind_executor(grpc_context, boost::asio::use_awaitable));
		
                    
    }
    //after done work
        bool finish_ok = co_await agrpc::finish(writer, grpc::Status::OK, boost::asio::bind_executor(grpc_context, boost::asio::use_awaitable));

I don't know how should I send you the stack trace, but I'll send a printscreen from the IDE.
Screenshot from 2022-05-06 10-17-50

What am I doing wrong?
Thank you in advance

@Tradias
Copy link
Owner

Tradias commented May 6, 2022

Hi, thank you for reporting the issue. I have trouble reproducing it. This email conversion in gRPC suggests that you might be doing two asynchronous operations in parallel that are not allowed to be in parallel. E.g. writing and finishing a writer at the same time.

I do not know what you mean by handle one request at a time, since you are using agrpc::repeatedly_request there will be one coroutine for each request from the client. In other words, the following code will co_spawn a coroutine for each client request, all those coroutines will be processed in parallel:

asio::awaitable<void> handle_request(grpc::ServerContext& server_context, auto& request, auto& writer, agrpc::GrpcContext& grpc_context)
{
  // This function will be called for each request from the client as soon as the request has been made, even while 
  // a previous request is still being processed by `handle_request`.
  // It is executing on a thread of the system_context.
  co_return;
}

void register_handler(asio::system_context& ctx, agrpc::GrpcContext& grpc_context, MarketDataAlert::AsyncService& service)
{
    agrpc::repeatedly_request(&MarketDataAlert::AsyncService::Requestsubscribe, service,
                              asio::bind_executor(grpc_context,
                                                  [&]<class T>(agrpc::RepeatedlyRequestContext<T>&& context)
                                                  {
                                                      
                                                      asio::co_spawn(
                                                              ctx,
                                                              [&, context = std::move(context)]()
                                                              {
                                                                  auto&& [server_context, request, responder] = context.args();
                                                                  return std::invoke(handle_request, server_context, request, responder, grpc_context);
                                                              },
                                                              asio::detached);
                                                  }));
}

@eduardokussler
Copy link
Author

Thank you for the very quick response! I'll take a look on the link you have sent. What I meant by "handle one request at a time", is that the first request is processed and receives it's messages but the following requests all receive a unable to connect to server, which I assume is because it is occupied by the first request, which is on the while(request_ok) loop. In other words, i can only have one stream active at a time. It is also happening with the code you provided.
This is the error received/generated by the client application: Message: failed to connect to all addresses subscribe rpc failed.

In other words, the following code will co_spawn a coroutine for each client request, all those coroutines will be processed in parallel

I thought that this would happen too, but apparently is not. I can only start another rpc call when the previous one finishes. Could this be a performance issue? I mean by this is: could my system be too slow to co_spawn and process the new request while the other one is running?

@Tradias
Copy link
Owner

Tradias commented May 6, 2022

I suspect that something is blocking the GrpcContext. E.g. in your example:

while(request_ok) {
		// If `fill_response` performs blocking operations, e.g. holding a lock on a mutex or calling some long
		// CPU-intensive task then the entire GrpcContext will be blocked, because `agrpc::write` resumes
		// in the thread of the GrpcContext. If that is the case then explicitly switch back to the system_context
		// before doing such tasks.
		co_await asio::post(asio::bind_executor(ctx, asio::use_awaitable));

		co_await fill_response(server_context, request, response, instrumentId);
 
		// do some work
                request_ok = co_await agrpc::write(writer, response,
                                                              boost::asio::bind_executor(grpc_context, boost::asio::use_awaitable));
		
                    
    }
    //after done work
        bool finish_ok = co_await agrpc::finish(writer, grpc::Status::OK, boost::asio::bind_executor(grpc_context, boost::asio::use_awaitable));

I have just adjusted one of the unit test to deliberately verify that repeatedly_request can handle multiple requests at the same time which succeeds on all tested platforms/compilers.

@eduardokussler
Copy link
Author

This was indeed the problem. Thank you! Keep up the good work with the library!

@Tradias
Copy link
Owner

Tradias commented May 9, 2022

Great, I am glad we figured it out and thank you for the kind words.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants