Giter Club home page Giter Club logo

asio-grpc's Introduction

asio-grpc

Reliability Rating

This library provides an implementation of boost::asio::execution_context that dispatches work to a grpc::CompletionQueue. Making it possible to write asynchronous gRPC servers and clients using C++20 coroutines, Boost.Coroutines, Boost.Asio's stackless coroutines, std::futures and callbacks. Also enables other Boost.Asio non-blocking IO operations like HTTP requests - all on the same CompletionQueue.

Example

Server side:

grpc::ServerBuilder builder;
std::unique_ptr<grpc::Server> server;
helloworld::Greeter::AsyncService service;
agrpc::GrpcContext grpc_context{builder.AddCompletionQueue()};
builder.AddListeningPort("0.0.0.0:50051", grpc::InsecureServerCredentials());
builder.RegisterService(&service);
server = builder.BuildAndStart();

boost::asio::co_spawn(
    grpc_context,
    [&]() -> boost::asio::awaitable<void>
    {
        grpc::ServerContext server_context;
        helloworld::HelloRequest request;
        grpc::ServerAsyncResponseWriter<helloworld::HelloReply> writer{&server_context};
        bool request_ok = co_await agrpc::request(&helloworld::Greeter::AsyncService::RequestSayHello, service,
                                                  server_context, request, writer);
        helloworld::HelloReply response;
        std::string prefix("Hello ");
        response.set_message(prefix + request.name());
        bool finish_ok = co_await agrpc::finish(writer, response, grpc::Status::OK);
    },
    boost::asio::detached);

grpc_context.run();
server->Shutdown();

snippet source | anchor

Client side:

auto stub =
    helloworld::Greeter::NewStub(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));
agrpc::GrpcContext grpc_context{std::make_unique<grpc::CompletionQueue>()};

boost::asio::co_spawn(
    grpc_context,
    [&]() -> boost::asio::awaitable<void>
    {
        grpc::ClientContext client_context;
        helloworld::HelloRequest request;
        request.set_name("world");
        std::unique_ptr<grpc::ClientAsyncResponseReader<helloworld::HelloReply>> reader =
            stub->AsyncSayHello(&client_context, request, agrpc::get_completion_queue(grpc_context));
        helloworld::HelloReply response;
        grpc::Status status;
        bool ok = co_await agrpc::finish(*reader, response, status);
    },
    boost::asio::detached);

grpc_context.run();

snippet source | anchor

Requirements

Tested:

  • gRPC 1.37
  • Boost 1.74
  • MSVC VS 2019 16.11
  • GCC 10.3
  • C++17 or C++20

For MSVC compilers the following compile definitions might need to be set:

BOOST_ASIO_HAS_DEDUCED_REQUIRE_MEMBER_TRAIT
BOOST_ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT
BOOST_ASIO_HAS_DEDUCED_EQUALITY_COMPARABLE_TRAIT
BOOST_ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT
BOOST_ASIO_HAS_DEDUCED_PREFER_MEMBER_TRAIT

Usage

The library can be added to a CMake project using either add_subdirectory or find_package . Once set up, include the following header:

#include <agrpc/asioGrpc.hpp>

As a subdirectory

Clone the repository into a subdirectory of your CMake project. Then add it and link it to your target.

add_subdirectory(/path/to/repository/root)
target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc)

As a CMake package

Clone the repository and install it.

mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/desired/installation/directory ..
cmake --build . --target install

Locate it and link it to your target.

# Make sure to set CMAKE_PREFIX_PATH to /desired/installation/directory
find_package(asio-grpc)
target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc)

Performance

asio-grpc is part of grpc_bench. Head over there to compare its performance against other libraries and languages.

Results from the helloworld unary RPC. Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, Linux, Boost 1.74, gRPC 1.30.2, asio-grpc v1.0.0

1 CPU server

name req/s avg. latency 90 % in 95 % in 99 % in avg. cpu avg. memory
rust_tonic_mt 44639 22.27 ms 9.63 ms 10.55 ms 572.53 ms 101.12% 16.06 MiB
rust_grpcio 39826 24.95 ms 26.31 ms 27.19 ms 28.45 ms 101.5% 30.46 MiB
rust_thruster_mt 38038 26.17 ms 11.39 ms 12.33 ms 673.02 ms 100.16% 13.17 MiB
cpp_grpc_mt 34954 28.53 ms 31.28 ms 31.75 ms 33.55 ms 101.93% 8.36 MiB
cpp_asio_grpc 34015 29.32 ms 32.05 ms 32.56 ms 34.41 ms 101.35% 7.72 MiB
go_grpc 6772 141.75 ms 287.57 ms 330.45 ms 499.47 ms 97.8% 28.07 MiB

2 CPU server

name req/s avg. latency 90 % in 95 % in 99 % in avg. cpu avg. memory
rust_tonic_mt 66253 14.33 ms 39.24 ms 59.11 ms 91.03 ms 201.2% 16.09 MiB
rust_grpcio 62678 15.38 ms 22.38 ms 24.81 ms 29.00 ms 201.38% 45.07 MiB
cpp_grpc_mt 62488 14.78 ms 31.76 ms 40.60 ms 60.79 ms 199.84% 24.9 MiB
cpp_asio_grpc 62040 14.91 ms 30.17 ms 37.77 ms 60.10 ms 199.6% 26.65 MiB
rust_thruster_mt 59204 16.22 ms 43.04 ms 71.87 ms 110.07 ms 199.31% 13.87 MiB
go_grpc 13978 63.48 ms 110.86 ms 160.62 ms 205.85 ms 198.23% 29.48 MiB

Documentation

The main workhorses of this library are the agrpc::GrpcContext and its executor_type - agrpc::GrpcExecutor.

The agrpc::GrpcContext implements boost::asio::execution_context and can be used as an argument to Boost.Asio functions that expect an ExecutionContext like boost::asio::spawn.

Likewise, the agrpc::GrpcExecutor models the Executor and Networking TS requirements and can therefore be used in places where Boost.Asio expects an Executor.

This library's API for RPCs is modeled closely after the asynchronous, tag-based API of gRPC. As an example, the equivalent for grpc::ClientAsyncReader<helloworld::HelloReply>.Read(helloworld::HelloReply*, void*) would be agrpc::read(grpc::ClientAsyncReader<helloworld::HelloReply>&, helloworld::HelloReply&, CompletionToken). It can therefore be helpful to refer to async_unary_call.h and async_stream.h while working with this library.

Instead of the void* tag in the gRPC API the functions in this library expect a CompletionToken. Boost.Asio comes with several CompletionTokens out of the box: C++20 coroutine, std::future, stackless coroutine, callback and Boost.Coroutine.

Getting started

Start by creating a agrpc::GrpcContext.

For servers and clients:

grpc::ServerBuilder builder;
agrpc::GrpcContext grpc_context{builder.AddCompletionQueue()};

snippet source | anchor

For clients only:

agrpc::GrpcContext grpc_context{std::make_unique<grpc::CompletionQueue>()};

snippet source | anchor

Add some work to the grpc_context (shown further below) and run it. Make sure to shutdown the server before destructing the grpc_context. Also destruct the grpc_context before destructing the server. A grpc_context can only be run on one thread at a time.

grpc_context.run();
server->Shutdown();
}  // grpc_context is destructed here before the server

snippet source | anchor

It might also be helpful to create a work guard before running the agrpc::GrpcContext to prevent grpc_context.run() from returning early.

auto guard = boost::asio::make_work_guard(grpc_context);

snippet source | anchor

Alarm

gRPC provides a grpc::Alarm which similar to boost::asio::steady_timer. Simply construct it and pass to it agrpc::wait with the desired deadline to wait for the specified amount of time without blocking the event loop.

grpc::Alarm alarm;
bool wait_ok = agrpc::wait(alarm, std::chrono::system_clock::now() + std::chrono::seconds(1), yield);

snippet source | anchor

wait_ok is true if the Alarm expired, false if it was canceled. (source)

Unary RPC Server-Side

Start by requesting a RPC. In this example yield is a boost::asio::yield_context, other CompletionTokens are supported as well, e.g. boost::asio::use_awaitable. The example namespace has been generated from example.proto.

grpc::ServerContext server_context;
example::v1::Request request;
grpc::ServerAsyncResponseWriter<example::v1::Response> writer{&server_context};
bool request_ok = agrpc::request(&example::v1::Example::AsyncService::RequestUnary, service, server_context,
                                 request, writer, yield);

snippet source | anchor

If request_ok is true then the RPC has indeed been started otherwise the server has been shutdown before this particular request got matched to an incoming RPC. For a full list of ok-values returned by gRPC see CompletionQueue::Next.

The grpc::ServerAsyncResponseWriter is used to drive the RPC. The following actions can be performed.

bool send_ok = agrpc::send_initial_metadata(writer, yield);

example::v1::Response response;
bool finish_ok = agrpc::finish(writer, response, grpc::Status::OK, yield);

bool finish_with_error_ok = agrpc::finish_with_error(writer, grpc::Status::CANCELLED, yield);

snippet source | anchor

Unary RPC Client-Side

On the client-side a RPC is initiated by calling the desired AsyncXXX function of the Stub

grpc::ClientContext client_context;
example::v1::Request request;
std::unique_ptr<grpc::ClientAsyncResponseReader<example::v1::Response>> reader =
    stub.AsyncUnary(&client_context, request, agrpc::get_completion_queue(grpc_context));

snippet source | anchor

The grpc::ClientAsyncResponseReader is used to drive the RPC.

bool read_ok = agrpc::read_initial_metadata(*reader, yield);

example::v1::Response response;
grpc::Status status;
bool finish_ok = agrpc::finish(*reader, response, status, yield);

snippet source | anchor

For the meaning of read_ok and finish_ok see CompletionQueue::Next.

Client-Streaming RPC Server-Side

Start by requesting a RPC.

grpc::ServerContext server_context;
grpc::ServerAsyncReader<example::v1::Response, example::v1::Request> reader{&server_context};
bool request_ok = agrpc::request(&example::v1::Example::AsyncService::RequestClientStreaming, service,
                                 server_context, reader, yield);

snippet source | anchor

Drive the RPC with the following functions.

bool send_ok = agrpc::send_initial_metadata(reader, yield);

example::v1::Request request;
bool read_ok = agrpc::read(reader, request, yield);

example::v1::Response response;
bool finish_ok = agrpc::finish(reader, response, grpc::Status::OK, yield);

snippet source | anchor

Client-Streaming RPC Client-Side

Start by requesting a RPC.

grpc::ClientContext client_context;
example::v1::Response response;
std::unique_ptr<grpc::ClientAsyncWriter<example::v1::Request>> writer;
bool request_ok = agrpc::request(&example::v1::Example::Stub::AsyncClientStreaming, stub, client_context, writer,
                                 response, yield);

snippet source | anchor

There is also a convenience overload that returns the grpc::ClientAsyncWriter at the cost of a sizeof(std::unique_ptr) memory overhead.

auto [writer, request_ok] =
    agrpc::request(&example::v1::Example::Stub::AsyncClientStreaming, stub, client_context, response, yield);

snippet source | anchor

With the grpc::ClientAsyncWriter the following actions can be performed to drive the RPC.

bool read_ok = agrpc::read_initial_metadata(*writer, yield);

example::v1::Request request;
bool write_ok = agrpc::write(*writer, request, yield);

bool writes_done_ok = agrpc::writes_done(*writer, yield);

grpc::Status status;
bool finish_ok = agrpc::finish(*writer, status, yield);

snippet source | anchor

For the meaning of read_ok, write_ok, writes_done_ok and finish_ok see CompletionQueue::Next.

Server-Streaming RPC Server-Side

Start by requesting a RPC.

grpc::ServerContext server_context;
example::v1::Request request;
grpc::ServerAsyncWriter<example::v1::Response> writer{&server_context};
bool request_ok = agrpc::request(&example::v1::Example::AsyncService::RequestServerStreaming, service,
                                 server_context, request, writer, yield);

snippet source | anchor

With the grpc::ServerAsyncWriter the following actions can be performed to drive the RPC.

bool send_ok = agrpc::send_initial_metadata(writer, yield);

example::v1::Response response;
bool write_ok = agrpc::write(writer, response, yield);

bool write_and_finish_ok = agrpc::write_and_finish(writer, response, grpc::WriteOptions{}, grpc::Status::OK, yield);

bool finish_ok = agrpc::finish(writer, grpc::Status::OK, yield);

snippet source | anchor

For the meaning of send_ok, write_ok, write_and_finish and finish_ok see CompletionQueue::Next.

Server-Streaming RPC Client-Side

Start by requesting a RPC.

grpc::ClientContext client_context;
example::v1::Request request;
std::unique_ptr<grpc::ClientAsyncReader<example::v1::Response>> reader;
bool request_ok =
    agrpc::request(&example::v1::Example::Stub::AsyncServerStreaming, stub, client_context, request, reader, yield);

snippet source | anchor

There is also a convenience overload that returns the grpc::ClientAsyncReader at the cost of a sizeof(std::unique_ptr) memory overhead.

auto [reader, request_ok] =
    agrpc::request(&example::v1::Example::Stub::AsyncServerStreaming, stub, client_context, request, yield);

snippet source | anchor

With the grpc::ClientAsyncReader the following actions can be performed to drive the RPC.

bool read_metadata_ok = agrpc::read_initial_metadata(*reader, yield);

example::v1::Response response;
bool read_ok = agrpc::read(*reader, response, yield);

grpc::Status status;
bool finish_ok = agrpc::finish(*reader, status, yield);

snippet source | anchor

For the meaning of read_metadata_ok, read_ok and finish_ok see CompletionQueue::Next.

Bidirectional-Streaming RPC Server-Side

Start by requesting a RPC.

grpc::ServerContext server_context;
grpc::ServerAsyncReaderWriter<example::v1::Response, example::v1::Request> reader_writer{&server_context};
bool request_ok = agrpc::request(&example::v1::Example::AsyncService::RequestBidirectionalStreaming, service,
                                 server_context, reader_writer, yield);

snippet source | anchor

With the grpc::ServerAsyncReaderWriter the following actions can be performed to drive the RPC.

bool send_ok = agrpc::send_initial_metadata(reader_writer, yield);

example::v1::Request request;
bool read_ok = agrpc::read(reader_writer, request, yield);

example::v1::Response response;
bool write_and_finish_ok =
    agrpc::write_and_finish(reader_writer, response, grpc::WriteOptions{}, grpc::Status::OK, yield);

bool write_ok = agrpc::write(reader_writer, response, yield);

bool finish_ok = agrpc::finish(reader_writer, grpc::Status::OK, yield);

snippet source | anchor

For the meaning of send_ok, read_ok, write_and_finish_ok, write_ok and finish_ok see CompletionQueue::Next.

Bidirectional-Streaming RPC Client-Side

Start by requesting a RPC.

grpc::ClientContext client_context;
std::unique_ptr<grpc::ClientAsyncReaderWriter<example::v1::Request, example::v1::Response>> reader_writer;
bool request_ok = agrpc::request(&example::v1::Example::Stub::AsyncBidirectionalStreaming, stub, client_context,
                                 reader_writer, yield);

snippet source | anchor

There is also a convenience overload that returns the grpc::ClientAsyncReaderWriter at the cost of a sizeof(std::unique_ptr) memory overhead.

auto [reader_writer, request_ok] =
    agrpc::request(&example::v1::Example::Stub::AsyncBidirectionalStreaming, stub, client_context, yield);

snippet source | anchor

With the grpc::ClientAsyncReaderWriter the following actions can be performed to drive the RPC.

bool read_metadata_ok = agrpc::read_initial_metadata(*reader_writer, yield);

example::v1::Request request;
bool write_ok = agrpc::write(*reader_writer, request, yield);

bool writes_done_ok = agrpc::writes_done(*reader_writer, yield);

example::v1::Response response;
bool read_ok = agrpc::read(*reader_writer, response, yield);

grpc::Status status;
bool finish_ok = agrpc::finish(*reader_writer, status, yield);

snippet source | anchor

For the meaning of read_metadata_ok, write_ok, writes_done_ok, read_ok and finish_ok see CompletionQueue::Next.

asio-grpc's People

Contributors

tradias avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.