Capy
Capy abstracts away sockets, files, and asynchrony with type-erased streams and buffer sequences—code compiles fast because the implementation is hidden. It provides the framework for concurrent algorithms that transact in buffers of memory: networking, serial ports, console, timers, and any platform I/O. This is only possible because Capy is coroutine-only, enabling optimizations and ergonomics that hybrid approaches must sacrifice.
What Capy Is
Capy is two things at once:
-
A protocol.
IoAwaitableis a protocol for propagating a coroutine’s execution environment—its executor, stop token, and allocator—forward throughco_awaitchains. This is the vocabulary that lets awaitable-based coroutine libraries interoperate. -
A reference implementation. A concrete library—thread pool, task types, byte streams, buffer sequences, synchronization primitives—that proves the protocol works in practice.
The protocol is the smaller, more general library living inside Capy. Without a shared protocol, N coroutine libraries need N×(N−1) adapters to interoperate; with one shared protocol for environment propagation, a single bridge covers everyone. This is the role IoAwaitable plays.
|
The core invariant: a coroutine always resumes on the executor it was launched with. Launch a coroutine on a strand, and every resumption—after every See the same-executor invariant for the rationale and bridging a foreign awaitable for the escape hatch. |
What Capy Is Not
Capy is not an all-purpose coroutine framework, and it is not an implementation detail of Corosio. It is the execution model and byte-stream layer—usable standalone for logic that operates on streams without any platform I/O (HTTP parsing, protocol state machines, serialization), and usable as the foundation for Corosio’s networking layer. CERN’s traccc project uses Capy without Corosio for GPU reconstruction pipelines; the Boost.HTTP parser is built entirely on Capy’s byte streams.
What This Library Does
-
Lazy coroutine tasks —
task<T>with forward-propagating stop tokens and automatic cancellation -
Buffer sequences — taken straight from Asio and improved
-
Stream concepts — seven coroutine stream concepts:
ReadStream,WriteStream,Stream,ReadSource,WriteSink,BufferSource,BufferSink -
Type-erased streams —
any_stream,any_read_stream,any_write_streamfor fast compilation -
Concurrency facilities — executors, strands, thread pools,
when_all,when_any -
Test utilities — mock streams, mock sources/sinks, error injection
What This Library Does Not Do
-
Networking — no sockets, acceptors, or DNS; that’s what Corosio provides
-
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
Target Audience
-
Users of Corosio — portable coroutine networking
-
Users of Http — sans-I/O HTTP/1.1 clients and servers
-
Users of Websocket — sans-I/O WebSocket
-
Users of Beast2 — high-level HTTP/WebSocket servers
-
Users of Burl — high-level HTTP client
All of these are built on Capy. Understanding its concepts—tasks, buffer sequences, streams, executors—unlocks the full power of the stack.
Design Philosophy
-
Use case first. Buffer sequences, stream concepts, executor affinity—these exist because I/O code needs them, not because they’re theoretically elegant.
-
Coroutines-only. No callbacks, futures, or sender/receiver. Hybrid support forces compromises; full commitment unlocks optimizations that adapted models cannot achieve.
-
Address the complaints of C++. Type erasure at boundaries, minimal dependencies, and hidden implementations keep builds fast and templates manageable.
Code Convention
|
Unless otherwise specified, all code examples in this documentation assume the following:
|
Quick Example
This example demonstrates a minimal coroutine that reads from a stream and echoes the data back:
#include <boost/capy.hpp>
using namespace boost::capy;
task<> echo(any_stream& stream)
{
char buf[1024];
for(;;)
{
auto [ec, n] = co_await stream.read_some(make_buffer(buf));
auto [wec, wn] = co_await write(stream, const_buffer(buf, n));
if(ec)
co_return;
if(wec)
co_return;
}
}
int main()
{
// In a real application, you would obtain a stream from Corosio,
// then launch the coroutine on its io_context and run it:
//
// corosio::io_context ioc;
// corosio::tcp_socket stream = /* from an acceptor or connect */;
// run_async(ioc.get_executor())(echo(stream));
// ioc.run();
}
The echo function accepts an any_stream&—a type-erased wrapper that works with any concrete stream implementation. The function reads data into a buffer, then writes it back. Both operations use co_await to suspend until the I/O completes.
The task<> return type (equivalent to task<void>) creates a lazy coroutine that does not start executing until awaited or launched with run_async.
Next Steps
-
Quick Start — Set up your first Capy project
-
C++20 Coroutines Tutorial — Learn coroutines from the ground up
-
Concurrency Tutorial — Understand threads, mutexes, and synchronization
-
Coroutines in Capy — Deep dive into
task<T>and the IoAwaitable protocol -
Buffer Sequences — Master the concept-driven buffer model
-
Stream Concepts — Understand the seven stream concepts