Skip to content

obi0ne/asio-grpc

 
 

Repository files navigation

asio-grpc

Reliability Rating Reliability Rating vcpkg 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

  • Server side 'hello world':

std::unique_ptr<grpc::Server> server;

grpc::ServerBuilder builder;
agrpc::GrpcContext grpc_context{builder.AddCompletionQueue()};
builder.AddListeningPort(host, grpc::InsecureServerCredentials());
helloworld::Greeter::AsyncService service;
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};
        co_await agrpc::request(&helloworld::Greeter::AsyncService::RequestSayHello, service, server_context,
                                request, writer);
        helloworld::HelloReply response;
        response.set_message("Hello " + request.name());
        co_await agrpc::finish(writer, response, grpc::Status::OK);
    },
    boost::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.

Looking for ideas

If you got any wishes or ideas for new features to asio-grpc then do not hesitate to open an issue. Some food for thought: A high-level API (what would that look like?), rate-limiting and protobuf::Arena support in agrpc::repeatedly_request, .... I am also happy to help with general gRPC and Asio questions.

Requirements

Tested by CI:

  • gRPC 1.44.0, 1.16.1 (older versions might work as well)
  • Boost 1.79.0 (min. 1.74.0)
  • Standalone Asio 1.17.0 (min. 1.17.0)
  • libunifex 2022-02-09
  • MSVC 19.31 (Visual Studio 17 2022)
  • GCC 8.4.0, 9.3.0, 10.3.0, 11.1.0
  • Clang 10.0.0, 11.0.0, 12.0.0
  • AppleClang 13.0.0.13000029
  • C++17 and C++20

For MSVC compilers and asio-grpc before v1.6.0 the following compile definitions 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_QUERY_STATIC_CONSTEXPR_MEMBER_TRAIT
BOOST_ASIO_HAS_DEDUCED_PREFER_MEMBER_TRAIT

When using standalone Asio then omit the BOOST_ prefix.

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 combined 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.

Using Boost.Asio:

find_package(gRPC)
find_package(Boost)
add_subdirectory(/path/to/repository/root)
target_link_libraries(your_app PUBLIC gRPC::grpc++ asio-grpc::asio-grpc Boost::headers)

Or using standalone Asio:

find_package(gRPC)
find_package(asio)
add_subdirectory(/path/to/repository/root)
target_link_libraries(your_app PUBLIC gRPC::grpc++ asio-grpc::asio-grpc-standalone-asio asio::asio)

Or using libunifex:

find_package(gRPC)
find_package(unifex)
add_subdirectory(/path/to/repository/root)
target_link_libraries(your_app PUBLIC gRPC::grpc++ asio-grpc::asio-grpc-unifex unifex::unifex)

As a CMake package

Clone the repository and install it.

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

CMake Options

ASIO_GRPC_USE_BOOST_CONTAINER - Use Boost.Container instead of <memory_resource>.

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

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, GCC 11.3.0, Boost 1.79.0, gRPC 1.45.2, asio-grpc v1.6.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
go_grpc 48531 19.91 ms 30.14 ms 33.31 ms 40.12 ms 101.89% 24.08 MiB
rust_thruster_mt 40478 24.55 ms 10.96 ms 12.74 ms 627.82 ms 104.12% 11.25 MiB
rust_tonic_mt 40472 24.53 ms 10.54 ms 11.53 ms 656.24 ms 100.81% 13.95 MiB
cpp_grpc_mt 38273 26.00 ms 27.70 ms 28.28 ms 29.93 ms 102.03% 5.49 MiB
rust_grpcio 37160 26.80 ms 28.57 ms 29.12 ms 30.16 ms 103.11% 16.99 MiB
cpp_asio_grpc_unifex 36932 26.95 ms 28.71 ms 29.16 ms 30.63 ms 102.42% 5.76 MiB
cpp_asio_grpc_callback 36910 26.96 ms 28.95 ms 29.55 ms 31.10 ms 102.38% 5.43 MiB
cpp_asio_grpc_cpp20_coroutine 32536 30.60 ms 32.83 ms 33.35 ms 34.54 ms 102.38% 5.57 MiB
cpp_asio_grpc_poll_context_coro 32261 30.87 ms 32.84 ms 33.28 ms 34.59 ms 97.29% 5.46 MiB
cpp_grpc_callback 10678 85.92 ms 110.19 ms 155.75 ms 169.82 ms 100.86% 46.71 MiB

2 CPU server

name req/s avg. latency 90 % in 95 % in 99 % in avg. cpu avg. memory
cpp_asio_grpc_unifex 84129 9.93 ms 15.18 ms 18.19 ms 26.90 ms 197.04% 29.37 MiB
cpp_grpc_mt 82149 10.13 ms 15.80 ms 19.14 ms 27.80 ms 197.31% 30.58 MiB
cpp_asio_grpc_callback 81859 10.21 ms 16.11 ms 19.63 ms 28.74 ms 201.94% 27.8 MiB
cpp_asio_grpc_cpp20_coroutine 76178 11.27 ms 17.79 ms 21.07 ms 29.60 ms 206.57% 27.12 MiB
cpp_grpc_callback 71953 11.33 ms 19.93 ms 24.16 ms 34.00 ms 207.53% 68.95 MiB
cpp_asio_grpc_poll_context_coro 69887 12.35 ms 21.89 ms 25.85 ms 36.03 ms 196.94% 30.94 MiB
go_grpc 67081 13.03 ms 20.37 ms 23.56 ms 30.64 ms 196.18% 25.92 MiB
rust_tonic_mt 59653 15.71 ms 41.71 ms 63.73 ms 98.95 ms 203.49% 15.97 MiB
rust_thruster_mt 59262 15.62 ms 44.36 ms 78.45 ms 100.23 ms 203.42% 12.91 MiB
rust_grpcio 58375 16.10 ms 23.77 ms 26.31 ms 32.31 ms 214.5% 30.29 MiB

Documentation

API reference

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 created by agrpc::use_sender(scheduler) 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{std::make_unique<grpc::CompletionQueue>()};

snippet source | anchor

Add some work to the grpc_context 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.

std::optional guard{asio::require(grpc_context.get_executor(), asio::execution::outstanding_work_t::tracked)};

snippet source | anchor

Where to go from here?

Check out the examples and the API documentation.

What users are saying

Asio-grpc abstracts away the implementation details of asynchronous grpc handling: crafting working code is easier, faster, less prone to errors and considerably more fun. At 3YOURMIND we reliably use asio-grpc in production since its very first release, allowing our developers to effortlessly implement low-latency/high-throughput asynchronous data transfer in time critical applications.

@3YOURMIND

Our project is a real-time distributed motion capture system that uses your framework to stream data back and forward between multiple machines. Previously I have tried to build a bidirectional streaming framework from scratch using only gRPC. However, it's not maintainable and error-prone due to a large amount of service and streaming code. As a developer whose experienced both raw grpc and asio-grpc, I can tell that your framework is a real a game-changer for writing grpc code in C++. It has made my life much easier. I really appreciate the effort you have put into this project and your superior skills in designing c++ template code.

@khanhha

About

Asynchronous gRPC with Asio/unified executors

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C++ 89.8%
  • CMake 10.2%