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
10 changes: 5 additions & 5 deletions .cursor/commands/doc-rebuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Regenerate the Capy documentation following this structure and style guide.
- Protocols — no HTTP, WebSocket, or TLS; see the Http and Beast2 libraries
- Platform event loops — no io_uring, IOCP, epoll, or kqueue; Capy is the layer above
- Callbacks or futures — coroutine-only means no other continuation styles
- Sender/receiver — Capy uses the IoAwaitable protocol, not `std::execution`
- Sender/receiver — Capy uses the ContextAwaitable protocol, not `std::execution`
4. **Target Audience** (verbatim):
- Users of Corosio — portable coroutine networking
- Users of Http — sans-I/O HTTP/1.1 clients and servers
Expand Down Expand Up @@ -153,10 +153,10 @@ This section transitions from general C++ knowledge to Capy-specific library usa
- `execution_context` — base class for custom contexts
- `strand` — serialization without mutexes
- Single-threaded vs multi-threaded patterns
- **The IoAwaitable Protocol** (`coroutines/io-awaitable.adoc`)
- **The ContextAwaitable Protocol** (`coroutines/context-awaitable.adoc`)
- The three-argument `await_suspend` signature
- Forward context propagation vs backward queries
- `IoAwaitable`, `IoAwaitableTask`, `IoLaunchableTask` concepts
- `ContextAwaitable` and `ContextRunnable` concepts
- Why affinity matters for I/O
- **Stop Tokens and Cancellation** (`coroutines/cancellation.adoc`)
**Teach from the ground up for complete beginners:**
Expand Down Expand Up @@ -215,7 +215,7 @@ This section transitions from general C++ knowledge to Capy-specific library usa
- Capy's answer: stop tokens flow downward through `co_await`
- `get_stop_token()` — retrieve the current stop token inside a task
- Automatic propagation: child tasks inherit parent's stop token
- No manual threading: the IoAwaitable protocol handles it
- No manual threading: the ContextAwaitable protocol handles it
*Part 4: Responding to Cancellation*
- Checking: `if (token.stop_requested()) co_return;`
- Cleanup: RAII ensures resources are released on early exit
Expand Down Expand Up @@ -248,7 +248,7 @@ This section transitions from general C++ knowledge to Capy-specific library usa
- `<capy/ex/run.hpp>`, `<capy/ex/run_async.hpp>`
- `<capy/ex/thread_pool.hpp>`, `<capy/ex/execution_context.hpp>`
- `<capy/ex/executor_ref.hpp>`, `<capy/ex/strand.hpp>`
- `<capy/concept/io_awaitable.hpp>`, `<capy/concept/io_awaitable_task.hpp>`
- `<capy/concept/context_awaitable.hpp>`, `<capy/concept/context_awaitable_task.hpp>`
- `<capy/concept/frame_allocator.hpp>`
- `<capy/when_all.hpp>`, `<capy/when_any.hpp>`

Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/code.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ alwaysApply: true

- test objects usually construct in fuse lambda
- `task<>` for `task<void>`
- awaitables satisfy concept IoAwaitable
- awaitables satisfy concept ContextAwaitable
2 changes: 1 addition & 1 deletion .cursor/rules/doc-awaitable.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Revised checklist:
- **`@return`** — Describe the returned aggregate and its elements. The first element is always `error_code`. Name and describe each subsequent element (e.g. "bytes_transferred", "endpoint"). Note that the result is customarily destructured by the caller.
- **Error conditions** — Document the notable `error_code` values or conditions that may appear in the first element. E.g. `capy::cond::canceled` if the stop token was activated or the i/o object's `cancel()` was called, EOF conditions, protocol-specific errors, etc. State that error codes should be compared to error conditions, not specific values.
- **`@throws`** — Typically only for precondition violations. State which preconditions trigger exceptions, or state that no exceptions are thrown during normal operation.
- **Cancellation** — State that the operation supports cancellation via `stop_token` propagated through the IoAwaitable protocol, or via the i/o object's `cancel()` member. State that the resulting error compares equal to `capy::cond::canceled`.
- **Cancellation** — State that the operation supports cancellation via `stop_token` propagated through the ContextAwaitable protocol, or via the i/o object's `cancel()` member. State that the resulting error compares equal to `capy::cond::canceled`.
- **`@par Example`** — Two or three `@code` blocks showing different usage patterns: typical happy path with destructuring, error handling, cancellation, different overloads, etc.
- **`@note` / `@par Remarks`** — Behavioral gotchas. E.g. "This operation may not read all of the requested bytes." Or equivalence to another overload.
- **`@tparam`** — For non-variadic template parameters. State the concept requirement (e.g. "The type must satisfy the *AsyncStream* concept"). Don't document `Args...`.
Expand Down
4 changes: 2 additions & 2 deletions .cursor/rules/doc-concept.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ alwaysApply: false
- **Brief** — One sentence naming the concept and stating what a conforming type does. E.g. "Concept for types that consume buffer data using callee-owned buffers."
- **Extended description** — A short paragraph summarizing the pattern the concept models, its purpose, and how it contrasts with related concepts.
- **`@tparam`** — One entry per template parameter to the concept.
- **`@par Syntactic Requirements`** — Bulleted `@li` list of every expression that must be valid. For each, state the member name, its arguments, and what it returns (including whether the return type must satisfy another concept like `IoAwaitable`). State the decomposed result tuple.
- **`@par Syntactic Requirements`** — Bulleted `@li` list of every expression that must be valid. For each, state the member name, its arguments, and what it returns (including whether the return type must satisfy another concept like `ContextAwaitable`). State the decomposed result tuple.
- **`@par Semantic Requirements`** — Grouped by operation. For each operation, a bulleted `@li` list describing its behavior, preconditions, postconditions, success/error semantics, and any state transitions (e.g. "no further operations are permitted after EOF").
- **`@par Buffer Lifetime`** (or equivalent resource lifetime section) — If the concept involves borrowed resources (buffers, handles, views), state exactly when they are valid and what invalidates them.
- **`@par Conforming Signatures`** — A single `@code` block showing the canonical signatures using pseudo-syntax (e.g. `IoAwaitable auto`). This is the quick-reference that implementers copy from.
- **`@par Conforming Signatures`** — A single `@code` block showing the canonical signatures using pseudo-syntax (e.g. `ContextAwaitable auto`). This is the quick-reference that implementers copy from.
- **`@par Example`** — A `@code` block showing a generic algorithm templated on the concept, demonstrating typical usage with destructuring, error handling, and interaction with related concepts.
- **`@see`** — Always last. Cross-references to related concepts, concrete models, and foundational concepts the signatures depend on.
4 changes: 2 additions & 2 deletions .cursor/rules/javadoc.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ globs:
alwaysApply: true
---

- Return io_result<T1,T2,T3> documented as "An awaitable yielding `(error_code, T1 ,T2, T3)`" ONLY type names (no inside spaces)
- io_result params (T1,T2,T3) meaning explained last in description
- Return `results<T1,T2,T3>` documented as "An awaitable yielding `(error_code, T1 ,T2, T3)`" ONLY type names (no inside spaces)
- `results` params (T1,T2,T3) meaning explained last in description
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ Capy is also used by [Beast2](https://github.com/cppalliance/beast2), which uses

The Beast2 family of libraries includes:

- **Capy** — The foundation of I/O.
- **Corosio** — Coroutine-only portable networking. This is the successor to Boost.Asio.
- **Http** — Sans-I/O HTTP/1.1. This is a high-level library: servers, clients, Express JS middleware.
- **Websocket** — Sans-I/O Websocket. This is also a high-level library.
- **Beast2** — High-level HTTP and WebSocket servers. Express.js-style routing, multithreaded, idiomatic C++.
- **Burl** — High-level HTTP client. The features of curl, the ergonomics of coroutines, and the design sensibility of Python Requests.
- **[Capy](https://github.com/cppalliance/capy)** — The foundation of I/O.
- **[Corosio](https://github.com/cppalliance/corosio)** — Coroutine-only portable networking. This is the successor to Boost.Asio.
- **[Http](https://github.com/cppalliance/http)** — Sans-I/O HTTP/1.1. This is a high-level library: servers, clients, Express JS middleware.
- **[Websocket](https://github.com/cppalliance/websocket)** — Sans-I/O Websocket. This is also a high-level library.
- **[Beast2](https://github.com/cppalliance/beast2)** — High-level HTTP and WebSocket servers. Express.js-style routing, multithreaded, idiomatic C++.
- **[Burl](https://github.com/cppalliance/burl)** — High-level HTTP client. The features of curl, the ergonomics of coroutines, and the design sensibility of Python Requests.

Currently, the C++ Standard does not deliver facilities optimized for networking I/O. We believe that Capy should become a standard library component to fill this gap. Our first paper based on Capy introduces the _IoAwaitable_ family of concepts:
Currently, the C++ Standard does not deliver facilities optimized for networking I/O. We believe that Capy should become a standard library component to fill this gap. Our first paper based on Capy introduces the _IoAwaitable_ family of concepts, which Capy now names _ContextAwaitable_:

- Paper: https://github.com/cppalliance/wg21-papers/blob/master/source/_reserve/d4003-io-awaitables.md

Expand All @@ -71,7 +71,7 @@ Capy represents Boost returning to its role as a leader in C++ standardization e

When an I/O operation completes, the operating system wakes up some thread, such as a completion port thread, an epoll reactor, or an io_uring worker. Without affinity tracking, your coroutine resumes on that arbitrary thread, forcing you to add synchronization everywhere or risk data races. This is the fundamental problem that coroutine-based networking must solve.

Capy's answer is the _IoAwaitable_ protocol. When you launch a coroutine with a designated executor, every child coroutine inherits that executor affinity automatically. Execution context flows forward through `co_await` chains, not backward through P3826 queries, to ensure every coroutine in the chain runs in the same context. When I/O completes on some OS thread, the _IoAwaitable_ protocol ensures your coroutine resumes on its designated executor. The data flow is explicit and testable. There are no thread-local globals, no implicit context, no surprises. Capy's `task` type uses the compiler to enforce invariants.
Capy's answer is the _ContextAwaitable_ protocol. When you launch a coroutine with a designated executor, every child coroutine inherits that executor affinity automatically. Execution context flows forward through `co_await` chains, not backward through P3826 queries, to ensure every coroutine in the chain runs in the same context. When I/O completes on some OS thread, the _ContextAwaitable_ protocol ensures your coroutine resumes on its designated executor. The data flow is explicit and testable. There are no thread-local globals, no implicit context, no surprises. Capy's `task` type uses the compiler to enforce invariants.

Cancellation follows the same forward-propagation model. Stop tokens flow forward from the launch of a coroutine chain alongside the execution context, to arrive at the platform API boundary, providing a uniform cancellation interface across all operations.

Expand All @@ -85,7 +85,7 @@ Capy is opinionated on the things that matter for I/O:
- Stop token integration: uniform cancellation, always available
- Allocator control over frame allocation with zero-overhead recycling
- Forward propagation of the full context through every `co_await`
- A `task` type that enforces the _IoAwaitable_ protocol at compile time
- A `task` type that enforces the _ContextAwaitable_ protocol at compile time
- Composition primitives for launching and coordinating coroutines
- A strand for safe concurrency without mutexes
- Buffer sequences: `std::ranges` for untyped bytes
Expand Down
8 changes: 4 additions & 4 deletions bench/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES
stdexec/main.cpp
stdexec/allocation_tracker.hpp
stdexec/awaitable_sender.hpp
stdexec/ioaw_io_read_stream.hpp
stdexec/ioaw_read_stream.hpp
stdexec/ioaw_sync_read_stream.hpp
stdexec/ctxaw_io_read_stream.hpp
stdexec/ctxaw_read_stream.hpp
stdexec/ctxaw_sync_read_stream.hpp
stdexec/sender_awaitable.hpp
stdexec/sender_io_env.hpp
stdexec/sender_await_context.hpp
stdexec/sndr_any_read_stream.hpp
stdexec/sndr_io_read_stream.hpp
stdexec/sndr_read_stream.hpp
Expand Down
52 changes: 26 additions & 26 deletions bench/beman/awaitable_sender.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
#ifndef BOOST_CAPY_BENCH_AWAITABLE_SENDER_HPP
#define BOOST_CAPY_BENCH_AWAITABLE_SENDER_HPP

#include <boost/capy/concept/io_awaitable.hpp>
#include <boost/capy/concept/context_awaitable.hpp>
#include <boost/capy/detail/await_suspend_helper.hpp>
#include <boost/capy/ex/await_context.hpp>
#include <boost/capy/ex/executor_ref.hpp>
#include <boost/capy/ex/io_env.hpp>
#include <boost/capy/io_result.hpp>
#include <boost/capy/results.hpp>

#include <beman/execution/execution.hpp>

Expand All @@ -31,7 +31,7 @@ namespace boost::capy {
// Query CPO for obtaining a Capy-compatible executor
// from a P2300 environment. The returned object must
// satisfy Capy's Executor concept. Environments that
// host IoAwaitables via the as_sender_lossy bridge must
// host ContextAwaitables via the as_sender_lossy bridge must
// answer this query.
struct get_io_executor_t
{
Expand Down Expand Up @@ -109,24 +109,24 @@ struct frame_cb

} // namespace detail

/** Sender that wraps an IoAwaitable.
/** Sender that wraps a ContextAwaitable.

When connected or co_awaited, the bridge queries
the receiver's or promise's environment for a
Capy-compatible executor via get_io_executor.
The executor is stored by value in the operation
state and used to construct the io_env passed to
the IoAwaitable's await_suspend.
state and used to construct the await_context passed to
the ContextAwaitable's await_suspend.

@tparam IoAw The IoAwaitable type.
@tparam CtxAw The ContextAwaitable type.
*/
template<class IoAw>
template<class CtxAw>
struct awaitable_sender
{
using sender_concept = beman::execution::sender_t;

using result_type = decltype(
std::declval<std::decay_t<IoAw>&>().await_resume());
std::declval<std::decay_t<CtxAw>&>().await_resume());

static auto make_sigs()
{
Expand Down Expand Up @@ -159,7 +159,7 @@ struct awaitable_sender

using completion_signatures = decltype(make_sigs());

IoAw aw_;
CtxAw aw_;

template<class Receiver>
struct op_state
Expand All @@ -173,13 +173,13 @@ struct awaitable_sender
std::declval<Receiver const&>()))
.query(get_io_executor_t{}));

IoAw aw_;
CtxAw aw_;
Receiver rcvr_;
executor_type ex_;
io_env env_;
await_context env_;
detail::frame_cb cb_;

op_state(IoAw aw, Receiver rcvr)
op_state(CtxAw aw, Receiver rcvr)
: aw_(std::move(aw))
, rcvr_(std::move(rcvr))
, ex_{}
Expand Down Expand Up @@ -299,7 +299,7 @@ struct awaitable_sender
beman::execution::get_stop_token_t{});
}

env_ = io_env{ex_, st, nullptr};
env_ = await_context{ex_, st, nullptr};

if(aw_.await_ready())
{
Expand Down Expand Up @@ -337,7 +337,7 @@ struct awaitable_sender
}

// Bypass beman's sender_awaitable when co_awaited
// from a bex::task. Adapts the IoAwaitable's 2-arg
// from a bex::task. Adapts the ContextAwaitable's 2-arg
// await_suspend to standard 1-arg protocol, avoiding
// the double bridge (as_sender_lossy + sender_awaitable).
template<class Promise>
Expand All @@ -362,10 +362,10 @@ struct awaitable_sender

struct aw
{
IoAw aw_;
CtxAw aw_;
executor_type ex_;
std::stop_token st_;
io_env env_;
await_context env_;

bool await_ready() noexcept
{
Expand All @@ -375,7 +375,7 @@ struct awaitable_sender
std::coroutine_handle<>
await_suspend(std::coroutine_handle<> h)
{
env_ = io_env{ex_, st_, nullptr};
env_ = await_context{ex_, st_, nullptr};
return aw_.await_suspend(h, &env_);
}

Expand All @@ -389,7 +389,7 @@ struct awaitable_sender
}
};

/** Create a beman::execution sender from an IoAwaitable.
/** Create a beman::execution sender from a ContextAwaitable.

The bridge routes the awaitable's result through sender
channels based on its type:
Expand All @@ -410,21 +410,21 @@ struct awaitable_sender
via get_io_executor. The environment must answer this
query with an object satisfying Capy's Executor concept.

@param aw The IoAwaitable to wrap.
@param aw The ContextAwaitable to wrap.
@return A sender whose completion channels reflect
the awaitable's result type.
*/
// Benchmark-only bridge. Unlike the canonical as_sender in
// example/awaitable-sender, this one accepts a compound io_result and
// example/awaitable-sender, this one accepts a compound `results` and
// splits it at runtime: set_value(n) on success, set_error(ec) on
// failure, dropping the byte count that accompanied the error. The
// benchmark keeps it to measure the bridge without the task<error_code>
// wrapper the canonical bridge requires; do not copy it into examples.
template<class IoAw>
auto as_sender_lossy(IoAw&& aw)
template<class CtxAw>
auto as_sender_lossy(CtxAw&& aw)
{
return awaitable_sender<std::decay_t<IoAw>>{
std::forward<IoAw>(aw)};
return awaitable_sender<std::decay_t<CtxAw>>{
std::forward<CtxAw>(aw)};
}

// -------------------------------------------------------
Expand Down
36 changes: 36 additions & 0 deletions bench/beman/ctxaw_io_read_stream.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Copyright (c) 2026 Steve Gerbino
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/capy
//

#ifndef BOOST_CAPY_BENCH_CTXAW_IO_READ_STREAM_HPP
#define BOOST_CAPY_BENCH_CTXAW_IO_READ_STREAM_HPP

#include "ctxaw_read_stream.hpp"

/// Abstract interface for ContextAwaitable read streams.
struct ctxaw_io_read_stream
{
virtual ctxaw_read_stream::read_awaitable
read_some(boost::capy::mutable_buffer) = 0;
virtual ~ctxaw_io_read_stream() = default;
};

/// Concrete implementation of ctxaw_io_read_stream wrapping
/// an ctxaw_read_stream.
struct ctxaw_io_read_stream_impl : ctxaw_io_read_stream
{
ctxaw_read_stream stream_;

ctxaw_read_stream::read_awaitable
read_some(boost::capy::mutable_buffer buf) override
{
return stream_.read_some(buf);
}
};

#endif
Loading
Loading