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.
In v3 the new ClientRPC and ServerRPC APIs will replace the current free-function API. Your feedback is highly appreciated. Please communicate via issues.
- Asio ExecutionContext compatible wrapper around grpc::CompletionQueue
- Support for all RPC types: unary, client-streaming, server-streaming and bidirectional-streaming with any mix of Asio CompletionToken as well as Sender, including allocator customization
- Support for asynchronously waiting for grpc::Alarms including cancellation through cancellation_slots and StopTokens
- Support for
std::execution
through either libunifex or stdexec - Support for generic gRPC clients and servers
- No extra codegen required, works with the vanilla gRPC C++ plugin (
grpc_cpp_plugin
) - No-Boost version with standalone Asio
- No-Asio version with libunifex or stdexec
- CMake function to easily generate gRPC source files: asio_grpc_protobuf_generate
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), libunifex or stdexec must be present and linked into your application.
Officially supported compilers are GCC 8+, Clang 10+, AppleClang 14+ and latest MSVC.
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 CMake 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++ and Boost::headers
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++ and asio::asio
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++ and unifex::unifex
Or using stdexec:
add_subdirectory(/path/to/asio-grpc)
target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc-stdexec)
# Also link with the equivalents of gRPC::grpc++ and STDEXEC::stdexec
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)
Or using stdexec:
# Make sure CMAKE_PREFIX_PATH contains /desired/installation/directory
find_package(asio-grpc)
target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc-stdexec)
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",
// To use the stdexec backend add
// "stdexec"
]
}
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)
# Or use the stdexec backend
#target_link_libraries(your_app PUBLIC asio-grpc::asio-grpc-stdexec)
Using Hunter
See asio-grpc's documentation on the Hunter website: https://hunter.readthedocs.io/en/latest/packages/pkg/asio-grpc.html.
Using conan
The recipe in conan-center is called asio-grpc.
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)
backend
- One of "boost" for Boost.Asio, "asio" for standalone Asio or "unifex" for libunifex.
Raw source code
This type of usage is unsupported. Future versions of asio-grpc might break it without notice.
Copy the contents of the src/
directory into your project and add it to your project's include directories. Depending on your desired backend: Boost.Asio,
standalone Asio, libunifex or stdexec, set the preprocessor definitions AGRPC_BOOST_ASIO
, AGRPC_STANDALONE_ASIO
, AGRPC_UNIFEX
or AGRPC_STDEXEC
respectively. Also make sure that
the backend's header files and libraries can be found correctly.
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++
.
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.52.1, asio-grpc v2.5.0, jemalloc 5.2.1
Request scenario: string_100B
Results
name | req/s | avg. latency | 90 % in | 95 % in | 99 % in | avg. cpu | avg. memory |
---|---|---|---|---|---|---|---|
rust_thruster_mt | 48796 | 20.19 ms | 9.42 ms | 12.11 ms | 516.23 ms | 104.51% | 12.06 MiB |
rust_tonic_mt | 43343 | 22.86 ms | 10.42 ms | 11.29 ms | 662.73 ms | 102.29% | 14.39 MiB |
go_grpc | 38541 | 25.33 ms | 38.74 ms | 42.98 ms | 53.94 ms | 100.0% | 25.19 MiB |
rust_grpcio | 34757 | 28.65 ms | 30.18 ms | 30.60 ms | 31.68 ms | 101.91% | 18.59 MiB |
cpp_grpc_mt | 33433 | 29.77 ms | 31.56 ms | 32.07 ms | 33.58 ms | 102.22% | 5.69 MiB |
cpp_asio_grpc_callback | 32521 | 30.61 ms | 32.54 ms | 33.14 ms | 35.29 ms | 101.65% | 5.93 MiB |
cpp_asio_grpc_unifex | 32507 | 30.62 ms | 32.50 ms | 32.99 ms | 34.66 ms | 102.94% | 5.81 MiB |
cpp_asio_grpc_coroutine | 28893 | 34.47 ms | 36.78 ms | 37.37 ms | 38.88 ms | 102.52% | 5.56 MiB |
cpp_asio_grpc_io_context_coro | 28072 | 35.47 ms | 37.77 ms | 38.22 ms | 39.93 ms | 77.73% | 5.39 MiB |
cpp_grpc_callback | 10243 | 90.44 ms | 118.77 ms | 164.20 ms | 175.43 ms | 100.62% | 44.9 MiB |
name | req/s | avg. latency | 90 % in | 95 % in | 99 % in | avg. cpu | avg. memory |
---|---|---|---|---|---|---|---|
cpp_grpc_mt | 87550 | 9.66 ms | 15.11 ms | 18.23 ms | 27.03 ms | 204.66% | 26.15 MiB |
cpp_asio_grpc_unifex | 86568 | 9.83 ms | 15.34 ms | 18.55 ms | 27.12 ms | 207.78% | 27.54 MiB |
cpp_asio_grpc_callback | 85292 | 10.03 ms | 15.38 ms | 18.51 ms | 26.62 ms | 206.63% | 24.73 MiB |
cpp_asio_grpc_coroutine | 79647 | 11.04 ms | 18.01 ms | 21.08 ms | 28.67 ms | 212.19% | 25.04 MiB |
cpp_asio_grpc_io_context_coro | 77953 | 11.24 ms | 18.32 ms | 21.61 ms | 29.20 ms | 161.24% | 28.4 MiB |
rust_thruster_mt | 75793 | 11.90 ms | 26.84 ms | 40.49 ms | 59.71 ms | 186.64% | 13.85 MiB |
cpp_grpc_callback | 68203 | 12.24 ms | 23.93 ms | 28.62 ms | 41.83 ms | 206.38% | 52.79 MiB |
rust_tonic_mt | 67162 | 13.85 ms | 34.05 ms | 46.31 ms | 69.58 ms | 206.13% | 17.24 MiB |
rust_grpcio | 60775 | 15.49 ms | 22.85 ms | 25.77 ms | 31.14 ms | 218.05% | 30.15 MiB |
go_grpc | 58192 | 15.87 ms | 24.31 ms | 27.10 ms | 32.43 ms | 197.71% | 25.06 MiB |
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 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::ClientRPC.read(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 Sender.
If you are interested in learning more about the implementation details of this library then check out this blog article.
Even more examples can be found in another repository.