Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions bench/bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ class test_executor
return h;
}

any_coro dispatch(any_coro h) const
{
return h;
}

// Post interface - resume inline for benchmarking
void post(any_coro h) const
{
Expand Down
12 changes: 6 additions & 6 deletions doc/modules/ROOT/pages/execution/executors.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ concept executor =
{ 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(h) } -> std::convertible_to<std::coroutine_handle<>>;
{ ce.post(h) };
{ ce.defer(h) };
};
Expand All @@ -101,8 +101,8 @@ concept executor =
|===
| Operation | Behavior

| `dispatch(h)`
| Run inline if safe, otherwise queue. Cheapest path.
| `operator()(h)`
| Run inline if safe, otherwise queue. Cheapest path. Also serves as dispatcher interface.

| `post(h)`
| Always queue, never inline. Guaranteed asynchrony.
Expand All @@ -113,7 +113,7 @@ concept executor =

**When to use each:**

* `dispatch` — Default choice. Allows the executor to optimize.
* `operator()` — 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).

Expand Down Expand Up @@ -171,7 +171,7 @@ Stoppable awaitables provide _both_ overloads of `await_suspend`.
Executors have specific thread safety guarantees:

* Copy constructor, comparison, `context()` — always thread-safe
* `dispatch`, `post`, `defer` — thread-safe for concurrent calls
* `operator()`, `post`, `defer` — thread-safe for concurrent calls
* `on_work_started`, `on_work_finished` — thread-safe, must not throw

== Executor Validity
Expand All @@ -184,7 +184,7 @@ thread_pool pool;
auto ex = pool.get_executor();
// When pool is destroyed, ex becomes invalid

// WARNING: Calling ex.dispatch() after pool destruction is undefined behavior
// WARNING: Calling ex() after pool destruction is undefined behavior
----

The copy constructor and `context()` remain valid until the context is
Expand Down
14 changes: 7 additions & 7 deletions doc/modules/ROOT/pages/execution/thread-pool.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ auto ex = pool.get_executor();
thread_pool& ctx = ex.context();

// Submit coroutines
ex.post(handle); // Queue for execution
ex.dispatch(handle); // Same as post (always queues)
ex.defer(handle); // Same as post
ex.post(handle); // Queue for execution
ex(handle); // Same as post (always queues)
ex.defer(handle); // Same as post

// Work tracking
ex.on_work_started();
ex.on_work_finished();
----

=== dispatch vs post vs defer
=== operator() vs post vs defer

For `thread_pool`, all three operations behave identically: they queue the
work for execution on a pool thread. The distinction matters for other
Expand All @@ -145,15 +145,15 @@ execution contexts:
| `post(h)`
| Always queue, never execute inline

| `dispatch(h)`
| `operator()(h)`
| Execute inline if safe, otherwise queue

| `defer(h)`
| Like post, but hints "this is my continuation"
|===

Since callers are never "inside" the thread pool's execution context,
`dispatch` always queues.
`operator()` always queues.

== Work Tracking

Expand Down Expand Up @@ -216,7 +216,7 @@ Services are shut down and destroyed when the pool is destroyed.
| `get_executor()`
| Safe

| `executor.post/dispatch/defer`
| `executor.post/operator()/defer`
| Safe (concurrent calls allowed)

| `executor.context()`
Expand Down
6 changes: 3 additions & 3 deletions include/boost/capy/concept/executor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ namespace capy {
completed. Precondition: a preceding call to
`on_work_started()` on an equal executor.

@li `dispatch(h)` - Execute a coroutine, potentially immediately
@li `operator()(h)` - Execute a coroutine, potentially immediately
if the executor determines it is safe to do so. The executor
may block forward progress of the caller until execution
completes.
completes. This also serves as the dispatcher interface.

@li `post(h)` - Queue a coroutine for later execution. The
executor shall not block forward progress of the caller
Expand Down Expand Up @@ -96,7 +96,7 @@ concept executor =
{ ce.on_work_finished() } noexcept;

// Work submission
{ ce.dispatch(h) } -> std::convertible_to<std::coroutine_handle<>>;
{ ce(h) } -> std::convertible_to<std::coroutine_handle<>>;
{ ce.post(h) };
{ ce.defer(h) };
};
Expand Down
42 changes: 14 additions & 28 deletions include/boost/capy/ex/strand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ namespace capy {
This class satisfies the `executor` concept, providing:
- `context()` - Returns the underlying execution context
- `on_work_started()` / `on_work_finished()` - Work tracking
- `dispatch(h)` - May run immediately if strand is idle
- `operator()(h)` - May run immediately if strand is idle
- `post(h)` - Always queues for later execution
- `defer(h)` - Same as post (continuation hint)

Expand Down Expand Up @@ -196,30 +196,6 @@ class strand
return impl_ == other.impl_;
}

/** Dispatch a coroutine through the strand.

If the calling thread is already executing within this strand,
the coroutine is resumed immediately via symmetric transfer,
bypassing the queue. This provides optimal performance but
means the coroutine may execute before previously queued work.

Otherwise, the coroutine is queued and will execute in FIFO
order relative to other queued coroutines.

@par Ordering
Callers requiring strict FIFO ordering should use post()
instead, which always queues the coroutine.

@param h The coroutine handle to dispatch.
@return A coroutine handle for symmetric transfer.
*/
// TODO: measure before deciding to split strand_impl for inlining fast-path check
any_coro
dispatch(any_coro h) const
{
return detail::strand_service::dispatch(*impl_, any_dispatcher(post_), h);
}

/** Post a coroutine to the strand.

The coroutine is always queued for execution, never resumed
Expand Down Expand Up @@ -254,16 +230,26 @@ class strand

/** Dispatch a coroutine through the strand.

This operator provides a dispatcher-style interface for
use with symmetric transfer. Equivalent to `dispatch()`.
If the calling thread is already executing within this strand,
the coroutine is resumed immediately via symmetric transfer,
bypassing the queue. This provides optimal performance but
means the coroutine may execute before previously queued work.

Otherwise, the coroutine is queued and will execute in FIFO
order relative to other queued coroutines.

@par Ordering
Callers requiring strict FIFO ordering should use post()
instead, which always queues the coroutine.

@param h The coroutine handle to dispatch.
@return A coroutine handle for symmetric transfer.
*/
// TODO: measure before deciding to split strand_impl for inlining fast-path check
any_coro
operator()(any_coro h) const
{
return dispatch(h);
return detail::strand_service::dispatch(*impl_, any_dispatcher(post_), h);
}
};

Expand Down
4 changes: 2 additions & 2 deletions include/boost/capy/ex/thread_pool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ class thread_pool::executor_type

Posts the coroutine to the thread pool and returns
immediately. The caller should suspend after calling
this function.
this function. Also serves as the dispatcher interface.

@param h The coroutine handle to execute.

@return A noop coroutine handle to resume.
*/
any_coro
dispatch(any_coro h) const
operator()(any_coro h) const
{
post(h);
return std::noop_coroutine();
Expand Down
2 changes: 1 addition & 1 deletion test/unit/ex/executor_work_guard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct guard_test_executor
}

std::coroutine_handle<>
dispatch(std::coroutine_handle<> h) const
operator()(std::coroutine_handle<> h) const
{
return h;
}
Expand Down
4 changes: 2 additions & 2 deletions test/unit/ex/strand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ struct strand_test
std::atomic<int> counter{0};

auto coro = make_counter_coro(counter);
s.dispatch(coro.handle());
s(coro.handle());
coro.release();

// Wait for work to complete
Expand Down Expand Up @@ -550,7 +550,7 @@ struct strand_test

std::atomic<int> counter{0};
auto coro = make_counter_coro(counter);
s.dispatch(coro.handle());
s(coro.handle());
coro.release();

BOOST_TEST(wait_for([&]{ return counter.load() >= 1; }));
Expand Down
4 changes: 2 additions & 2 deletions test/unit/ex/thread_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ struct thread_pool_test
thread_pool pool(1);
auto ex = pool.get_executor();

// dispatch returns noop_coroutine (always posts for thread_pool)
auto result = ex.dispatch(std::noop_coroutine());
// operator() returns noop_coroutine (always posts for thread_pool)
auto result = ex(std::noop_coroutine());
BOOST_TEST(result == std::noop_coroutine());
}

Expand Down
2 changes: 1 addition & 1 deletion test/unit/executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ struct test_executor

// Work submission
std::coroutine_handle<>
dispatch(std::coroutine_handle<> h) const
operator()(std::coroutine_handle<> h) const
{
return h;
}
Expand Down
Loading