Skip to content

parisa-hr/asio-grpc

 
 

Repository files navigation

asio-grpc

Reliability Rating Reliability Rating vcpkg conan hunter

An Executor, Networking TS and std::execution interface to grpc::CompletionQueue for writing asynchronous gRPC clients and servers using C++20 coroutines, Boost.Coroutines, Asio's stackless coroutines, callbacks, sender/receiver and more.

Features

Example

  • Client-side 'hello world':

helloworld::Greeter::Stub stub{grpc::CreateChannel(host, grpc::InsecureChannelCredentials())};
agrpc::GrpcContext grpc_context;

asio::co_spawn(
    grpc_context,
    [&]() -> asio::awaitable<void>
    {
        using RPC = agrpc::RPC<&helloworld::Greeter::Stub::PrepareAsyncSayHello>;
        grpc::ClientContext client_context;
        helloworld::HelloRequest request;
        request.set_name("world");
        helloworld::HelloReply response;
        status = co_await RPC::request(grpc_context, stub, client_context, request, response, asio::use_awaitable);
        std::cout << status.ok() << " response: " << response.message() << std::endl;
    },
    asio::detached);

grpc_context.run();

snippet source | anchor

More examples for things like streaming RPCs, double-buffered file transfer with io_uring, libunifex-based coroutines, sharing a thread with an io_context and generic clients/servers can be found in the example directory. Even more examples can be found in another repository.

Requirements

Asio-grpc is a C++17, header-only library. To install it, CMake (3.14+) is all that is needed.

To use it, gRPC and either Boost.Asio (min. 1.74.0), standalone Asio (min. 1.17.0) or libunifex must be present and linked into your application.

Supported compilers are GCC 8+, Clang 10+, AppleClang 14+ and latest MSVC.

Usage

The library can be added to a CMake project using either add_subdirectory or find_package. Once set up, include the individual headers from the agrpc/ directory or the convenience header:

#include <agrpc/asio_grpc.hpp>
As a subdirectory

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

Using Boost.Asio:

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

# Also link with the equivalents of gRPC::grpc++_unsecure, Boost::headers and
# Boost::container (if ASIO_GRPC_USE_BOOST_CONTAINER has been set)

Or using standalone Asio:

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

# Also link with the equivalents of gRPC::grpc++_unsecure, asio::asio and
# Boost::container (if ASIO_GRPC_USE_BOOST_CONTAINER has been set)

Or using libunifex:

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

# Also link with the equivalents of gRPC::grpc++_unsecure, unifex::unifex and
# Boost::container (if ASIO_GRPC_USE_BOOST_CONTAINER has been set)

Set optional options before calling add_subdirectory. Example:

set(ASIO_GRPC_USE_BOOST_CONTAINER on)
add_subdirectory(/path/to/asio-grpc)

As a CMake package

Clone the repository and install it. Append any optional options like -DASIO_GRPC_USE_BOOST_CONTAINER=on to the cmake configure call.

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

Locate it and link it to your target.

Using Boost.Asio:

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

Or using standalone Asio:

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

Or using libunifex:

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

Using vcpkg

Add asio-grpc to the dependencies inside your vcpkg.json:

{
    "name": "your_app",
    "version": "0.1.0",
    "dependencies": [
        "asio-grpc",
        // To use the Boost.Asio backend add
        // "boost-asio",
        // To use the standalone Asio backend add
        // "asio",
        // To use the libunifex backend add
        // "libunifex"
    ]
}

Locate asio-grpc and link it to your target in your CMakeLists.txt:

find_package(asio-grpc)
# Using the Boost.Asio backend
target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc)
# Or use the standalone Asio backend
#target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc-standalone-asio)
# Or use the libunifex backend
#target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc-unifex)

Available features

boost-container (deprecated) - Use Boost.Container instead of <memory_resource>.

See selecting-library-features to learn how to select features with vcpkg.

Using Hunter

See asio-grpc's documentation on the Hunter website: https://hunter.readthedocs.io/en/latest/packages/pkg/asio-grpc.html.

Using conan

Please refer to the conan documentation on how to use packages. The recipe in conan-center is called asio-grpc/2.4.0.
If you are using conan's CMake generator then link with asio-grpc::asio-grpc independent of the backend that you choose:

find_package(asio-grpc)
target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc)

Available options

backend - One of "boost" for Boost.Asio, "asio" for standalone Asio or "unifex" for libunifex.

local_allocator (deprecated) - One of "memory_resource" for <memory_resource>, "boost_container" for Boost.Container, "recycling_allocator" for asio::recycling_allocator.

CMake Options

ASIO_GRPC_USE_BOOST_CONTAINER (deprecated) - Use Boost.Container instead of <memory_resource>. Mutually exclusive with ASIO_GRPC_USE_RECYCLING_ALLOCATOR.

ASIO_GRPC_USE_RECYCLING_ALLOCATOR (deprecated) - Use asio::recycling_allocator instead of <memory_resource>. Mutually exclusive with ASIO_GRPC_USE_BOOST_CONTAINER.

ASIO_GRPC_DISABLE_AUTOLINK - Set before using find_package(asio-grpc) to prevent asio-grpcConfig.cmake from finding and setting up interface link libraries like gRPC::grpc++.

Performance

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

Below are the results from the helloworld unary RPC for:
Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
Linux, GCC 12.2.0, Boost 1.80.0, gRPC 1.50.0, asio-grpc v2.4.0, jemalloc 5.2.1
Request scenario: string_100B

Results

1 CPU server

name req/s avg. latency 90 % in 95 % in 99 % in avg. cpu avg. memory
rust_thruster_mt 48754 20.22 ms 9.38 ms 11.23 ms 513.03 ms 103.97% 11.94 MiB
rust_tonic_mt 42770 23.17 ms 10.34 ms 11.10 ms 669.23 ms 102.71% 14.43 MiB
go_grpc 38472 25.39 ms 38.61 ms 43.03 ms 54.13 ms 98.99% 25.28 MiB
rust_grpcio 35048 28.41 ms 29.84 ms 30.30 ms 32.65 ms 102.94% 17.74 MiB
cpp_grpc_mt 33371 29.82 ms 31.66 ms 32.58 ms 34.83 ms 102.76% 5.76 MiB
cpp_asio_grpc_unifex 32721 30.43 ms 32.25 ms 32.86 ms 35.44 ms 102.44% 5.46 MiB
cpp_asio_grpc_callback 32492 30.64 ms 32.45 ms 33.12 ms 35.11 ms 103.82% 5.48 MiB
cpp_asio_grpc_coroutine 29089 34.23 ms 36.27 ms 37.10 ms 38.94 ms 101.86% 5.53 MiB
cpp_asio_grpc_io_context_coro 28157 35.37 ms 37.57 ms 38.51 ms 41.34 ms 78.5% 5.36 MiB
cpp_grpc_callback 10142 90.92 ms 123.49 ms 165.29 ms 176.71 ms 102.57% 43.26 MiB

2 CPU server

name req/s avg. latency 90 % in 95 % in 99 % in avg. cpu avg. memory
cpp_grpc_mt 87665 9.71 ms 15.19 ms 18.19 ms 26.77 ms 206.15% 25.01 MiB
cpp_asio_grpc_unifex 87236 9.76 ms 15.41 ms 18.46 ms 26.22 ms 205.13% 26.0 MiB
cpp_asio_grpc_callback 85191 10.02 ms 15.35 ms 18.37 ms 26.49 ms 206.83% 22.68 MiB
cpp_asio_grpc_coroutine 79233 11.10 ms 18.17 ms 21.42 ms 28.67 ms 206.0% 25.27 MiB
cpp_asio_grpc_io_context_coro 76520 11.53 ms 19.07 ms 22.44 ms 29.86 ms 158.35% 27.67 MiB
rust_thruster_mt 76448 11.76 ms 26.13 ms 37.99 ms 58.98 ms 189.37% 14.71 MiB
rust_tonic_mt 67477 13.80 ms 33.91 ms 46.42 ms 69.08 ms 210.09% 16.88 MiB
cpp_grpc_callback 65782 12.65 ms 25.92 ms 31.98 ms 47.67 ms 203.9% 58.74 MiB
rust_grpcio 61248 15.38 ms 23.20 ms 25.78 ms 31.02 ms 217.6% 28.53 MiB
go_grpc 58734 15.71 ms 24.00 ms 26.84 ms 32.40 ms 198.43% 26.55 MiB

Documentation

Documentation

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

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

Likewise, the agrpc::GrpcExecutor satisfies the Executor and Networking TS and Scheduler requirements and can therefore be used in places where Asio/libunifex expects an Executor or Scheduler.

The 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).

Instead of the void* tag in the gRPC API the functions in this library expect a CompletionToken. Asio comes with several CompletionTokens already: C++20 coroutine, stackless coroutine, callback and Boost.Coroutine. There is also a special token called agrpc::use_sender that causes RPC functions to return a TypedSender.

If you are interested in learning more about the implementation details of this library then check out this blog article.

Getting started

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;

snippet source | anchor

Add some work to the grpc_context and run it. As an example, a simple unary request using asio::use_awaitable (the default completion token):

example::v1::Example::Stub stub(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));
asio::co_spawn(
    grpc_context,
    [&]() -> asio::awaitable<void>
    {
        grpc::ClientContext client_context;
        example::v1::Request request;
        request.set_integer(42);
        example::v1::Response response;
        using RPC = agrpc::RPC<&example::v1::Example::Stub::PrepareAsyncUnary>;
        grpc::Status status = co_await RPC::request(grpc_context, stub, client_context, request, response);
        assert(status.ok());
    },
    asio::detached);
grpc_context.run();

snippet source | anchor

Where to go from here?

Check out the examples and the documentation.

About

Asynchronous gRPC with Asio/unified executors

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages

  • C++ 93.0%
  • CMake 7.0%