Skip to content

feat: add coroutine support for client-side asynchronous calls#527

Open
alexcani wants to merge 9 commits intoKistler-Group:masterfrom
alexcani:alexcani/awaitables
Open

feat: add coroutine support for client-side asynchronous calls#527
alexcani wants to merge 9 commits intoKistler-Group:masterfrom
alexcani:alexcani/awaitables

Conversation

@alexcani
Copy link
Copy Markdown

@alexcani alexcani commented Mar 1, 2026

C++ 20 introduced coroutine support to the language, enabling writing asynchronous code using the co_await, co_return and co_yield operators. Similar to other languages, coroutines allow functions to suspend execution at certain points and later resume from where they left off, without blocking the calling thread.

Coroutines are a natural fit for any sort of IO-bound asynchronous operations, such as D-Bus communication.

This PR introduces native coroutine support in sdbus-c++ by the means of the Awaitable<T> class, a type that implements the C++20 awaitable protocol, allowing D-Bus method calls to be awaited in coroutines.

Summary of the changes:

Core library

  • New Awaitable<T> type that can be co_awaited, which suspends a running coroutine. When the result or error of a method call arrives, the coroutine is resumed and the result/error is returned.
    Implementation is done in a thread-safe way, meaning there are no race conditions between the awaitable object returned by asynchronous calls and the callback invoked by the event loop upon arrival of a message. Naturally, it works in the single-threaded scenario as well, where the connection event loop may be driven externally and/or integrated in a full fledged coroutine runtime.
  • Low-level API: new callMethodAsync() overloads accepting with_awaitable_t tag and returning Awaitable<MethodReply>.
  • High-level API: new getResultAsAwaitable methods in the relevant high-level API helpers, covering methods and properties. StandardInterfaces.h classes have also been updated to expose awaitable-based methods.
  • Integration tests for both low-level and high-level APIs.

Codegen

  • Added support for generating awaitable-based async methods in the xml2cpp tool through new values for the org.freedesktop.DBus.Method.Async.ClientImpl and org.freedesktop.DBus.Property.[Get/Set].Async.ClientImpl annotations.

alexcani added 7 commits March 1, 2026 18:01
Include .vscode/ in .gitignore to prevent IDE-specific files
from being tracked in the repository.
Prepare for C++20 coroutine support for asynchronous D-Bus method
calls by introducing the Awaitable<T> class, enabling co_await syntax.
This class contains the three necessary methods to support the co_await
operator: await_ready, await_suspend and await_resume.

A shared data structure is used to hold data needed by the callback
that will resume the coroutine and feed in the result or exception. This
work similarly to how a future/promise pair work.
Potential race conditions in the multithreaded scenario are covered by
atomic operations on an AwaitableState value, preventing situations
that could lead to a double coroutine resume or permanent suspension.
Add three new flavors of the callMethodAsync method in the
IProxy interface that use the with_awaitable_t tag to return an
Awaitable<MethodReply>.
Implementation relies on the callback-based mechanism to
insert the results into the shared AwaitableData, transition
the state to Completed and resume the coroutine.
Add a .getResultAsAwaitable method to each of the relevant
high-level API helpers (AsyncMethodInvoker, AsyncPropertyGetter,
AsyncPropertySetter, and AsyncAllPropertiesGetter).
Implementation is similar to the low-level API: rely on the
.uponReplyInvoke() mechanism to set the results on the
shared data, atomically flip the state, and resume the suspended
coroutine.
Adapt the StandardInterface.h classes to include an awaitable-based
overload of the async methods.
…ation

The current implementation relies on two flags to determine if async is
enabled for methods/properties.
In preparation to introduce the generation of the coroutine-friendly
awaitable methods as an async implementation, refactor this as an enum
containing the implementations and an std::optional to mark simultaneously
if async is enabled and the method. There are no behavioral changes or
new functonality.
C++ 17 is needed for std::optional, therefore the used C++ standard
for the tool has been changed.
Use the new awaitable-returning methods from the high level
API to add support to a third async implementation mode
alongside the existing callback and future implementations.

Add a new enum entry to AsyncImpl and parse the "awaitable"
or "coroutine" values in the relevant annotations for methods
and properties.

Methods annotated with this implementation will return
sdbus::Awaitable<ReturnType> and call .getResultAsAwaitable<>() on the
high-level API. Properties follow the same pattern, returning
sdbus::Awaitable<sdbus::Variant> for getters and sdbus::Awaitable<void>
for setters.

The nested ternary expressions for determining return types have been
refactored into explicit if-else blocks for improved readability and to
accommodate the additional implementation type. The existing callback
and future implementations remain unchanged in behavior.
@alexcani
Copy link
Copy Markdown
Author

alexcani commented Mar 1, 2026

Hi there!
There are a few things I would appreciate your help clarifying:

  • I tried my best to follow the existing code style. It would be great to have something like a .clang-format file in the project, though.
  • I was not sure how to handle the copyright part. I added my name/email to new files that I've created, and left the other ones untouched. Please let me know if I should do it differently
  • This is C++20, however we claim to be backward-compatible to C++17 at the public API level. This breaks it. I'd like your input on how to handle it. We could conditionally compile this based on the presence of "__cpp_impl_coroutine" feature macro.
  • I left the constructor for Awaitable public to avoid needing to befriend details::Proxy AND AsyncMethodInvoker. this also removed the need for forward declarations in the file. i thought it looked good and accepted the tradeoff. i can change it if you prefer to have all "infrastructure" types constructors private.
  • The integration tests are mostly a replic of the ones for async methods. To be completely transparent, i had Claude write 98% of the tests (but i did review the code myself), meaning I didn't put much effort in coming with new test cases. Let me know if there's a specific corner case you'd like to test

Also thanks for the work you put in the project, I always had a good time using it. Please let me know if there are further changes or design considerations needed

alexcani added 2 commits March 1, 2026 20:36
Introduce new awaitable methods in TestProxy for asynchronous
operations and add corresponding integration tests in
DBusAwaitableMethodsTests.cpp to validate functionality.

The test uses a simple coroutine task type which uses a
promise/future pair to pass data between the callback
and the coroutine, leveraging the fact that the tests are all
multithreaded.
This approach would not work in a single-threaded scenario,
as the future's .get() method would block the thread.
Update the main tutorial with the new awaitable-based
async method.
@alexcani alexcani force-pushed the alexcani/awaitables branch from e8ceebb to 3988cd7 Compare March 1, 2026 19:36
@sangelovic
Copy link
Copy Markdown
Collaborator

Hey @alexcani. Thanks a lot for contributing and submitting the PR! I started looking into that but am a bit overloaded currently, I'll definitely come back to this asap.

@sangelovic
Copy link
Copy Markdown
Collaborator

sangelovic commented Mar 25, 2026

@alexcani

Hi! I reviewed the PR. Appears to me to be one of the best, well-done PRs I've reviewed on this project. It is enjoyable to see changes done to nice little details and across all levels. I have no experience with coroutines, so I had to do the research first. Still, given lack of experience, I don't feel 100% confident about all the details of good coroutines design. But given the quality of your work, I think I can trust you.

However, I did some deeper reflection about things like overall design, thread coordination, exception handling, etc. -- and when I tried to think about them out-of-the-box, I got back to your solution, understanding your motives. Again, I see you've put an aspect of quality and craftsmanship to your work, and this is something I highly appreciate.

One thing I was asking myself was -- in AwaitableData template, could we simply use std::variant<> for both the result and the exception, instead of std::optional for one and the other? I think that this

  • would idiomatically be more self-revealing that only one of those is available at a time, and thus would eliminate potential confusion of a reader about combinations resulting from having two std::optionals (like can it be that both are set at the same time? Or can it be that both are unset?),
  • would need only one member instead of always two, making it simpler,
  • while introducing no extra performance penalty.

I've already prepared a local patch for that. It works nicely. I can push that to your branch if you're okay with that, for you to review. Let me know. In the code I touched, I also changed the formatting a bit to stay consistent with the surrounding code.

Regarding code style, yes, clang-format would be great. I have that on my TODO list.

Regarding copyright, all fine with me.

Regarding API, yes, it is currently C++17 compatible. Would be great to use feature macros to have this feature conditionally available.

Regarding Awaitable public c-tor, I don't have a strong opinion, but for consistency with other similar solutions in the library, I would slightly tend towards private c-tor and befriending the relevant classes. It's already done this way in other places in the library. Additionally, I would add assert(data_ != nullptr); in the c-tor body to express the invariant of always non-null ptr clearly.

Tests LGTM.

Clang-tidy job is failing -- shall I address the reported issues, or will you?

I'm glad you've enjoyed using the library so far. That was my ultimate design goal, and I've put my best into it.

@alexcani
Copy link
Copy Markdown
Author

alexcani commented Mar 30, 2026 via email

@sangelovic
Copy link
Copy Markdown
Collaborator

OK, I'll commit my patch. And no problem -- if I find time then I may do the adjustments myself. If not, you can do it when you are back. Enjoy your vacation.

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

Successfully merging this pull request may close these issues.

2 participants