Executors
This page explains the executor and dispatcher concepts that underpin Capy’s execution model.
Code snippets assume using namespace boost::capy; is in effect.
|
Two Concepts, Two Levels
Capy distinguishes between two related but different concepts:
| Concept | Purpose | Typical Use |
|---|---|---|
|
Schedule a coroutine handle for resumption |
Internal plumbing in awaitables |
|
Full execution context interface |
User-facing API for work submission |
A dispatcher is simpler: just a callable that accepts a coroutine handle. An executor adds work tracking, context access, and multiple submission methods.
The Dispatcher Concept
A dispatcher is a callable that schedules coroutine resumption:
template<typename D, typename P = void>
concept dispatcher = requires(D const& d, std::coroutine_handle<P> h) {
{ d(h) } -> std::convertible_to<coro>;
};
When invoked with a coroutine handle, the dispatcher:
-
Schedules the handle for resumption (inline or queued)
-
Returns a handle suitable for symmetric transfer
The Executor Concept
An executor provides the full interface for scheduling work:
template<class E>
concept executor =
std::copy_constructible<E> &&
std::equality_comparable<E> &&
requires(E const& ce, std::coroutine_handle<> h) {
{ ce.context() } -> /* reference to execution context */;
{ ce.on_work_started() } noexcept;
{ ce.on_work_finished() } noexcept;
{ ce.dispatch(h) } -> std::convertible_to<std::coroutine_handle<>>;
{ ce.post(h) };
{ ce.defer(h) };
};
Scheduling Operations
| Operation | Behavior |
|---|---|
|
Run inline if safe, otherwise queue. Cheapest path. |
|
Always queue, never inline. Guaranteed asynchrony. |
|
Always queue with "this is my continuation" hint. Enables optimizations. |
When to use each:
-
dispatch— Default choice. Allows the executor to optimize. -
post— When you need guaranteed asynchrony (e.g., releasing a lock first). -
defer— When posting your own continuation (enables thread-local queuing).
Work Tracking
The on_work_started() and on_work_finished() calls track outstanding work.
This enables run() to know when to stop:
executor ex = ctx.get_executor();
ex.on_work_started(); // Increment work count
// ... submit work ...
ex.on_work_finished(); // Decrement work count
The executor_work_guard RAII wrapper simplifies this pattern.
Affine Awaitable Concept
Awaitables that participate in affinity propagation implement affine_awaitable:
template<typename A, typename D, typename P = void>
concept affine_awaitable =
dispatcher<D, P> &&
requires(A a, std::coroutine_handle<P> h, D const& d) {
a.await_suspend(h, d);
};
The awaitable receives the dispatcher in await_suspend and uses it to
resume the caller when the operation completes.
Stoppable Awaitable Concept
Awaitables with cancellation support implement stoppable_awaitable:
template<typename A, typename D, typename P = void>
concept stoppable_awaitable =
affine_awaitable<A, D, P> &&
requires(A a, std::coroutine_handle<P> h, D const& d, std::stop_token t) {
a.await_suspend(h, d, t);
};
Stoppable awaitables provide both overloads of await_suspend.
Thread Safety
Executors have specific thread safety guarantees:
-
Copy constructor, comparison,
context()— always thread-safe -
dispatch,post,defer— thread-safe for concurrent calls -
on_work_started,on_work_finished— thread-safe, must not throw
Executor Validity
An executor becomes invalid when its execution context shuts down:
io_context ctx;
auto ex = ctx.get_executor();
ctx.stop(); // Begin shutdown
// WARNING: Calling ex.dispatch() now is undefined behavior
The copy constructor and context() remain valid until the context is
destroyed, but work submission functions become undefined.
When NOT to Use Executors Directly
Use executors directly when:
-
Implementing custom I/O operations
-
Building framework-level abstractions
-
Integrating with external event loops
Do NOT use executors directly when:
-
Writing application code — use
async_runandtask<T>instead -
You just need to run some code later — use the higher-level abstractions
Summary
| Concept | Purpose |
|---|---|
|
Minimal interface for coroutine resumption |
|
Full work submission with tracking |
|
Awaitable that accepts dispatcher for affinity |
|
Awaitable that also accepts stop token |
Next Steps
-
Execution Contexts — Service management and thread pools
-
executor concept — Reference documentation