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. IoAwaitable is a protocol for propagating a coroutine’s execution environment—its executor, stop token, and allocator—forward through co_await chains. 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 co_await—happens on that strand. Shared state touched between suspension points is free of data races without a mutex. A plain awaitable can resume a coroutine on any thread and would break this guarantee, so Capy rejects it at compile time and provides an explicit way to bridge such awaitables when you need one.

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 taskstask<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 streamsany_stream, any_read_stream, any_write_stream for 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.

Requirements

Assumed Knowledge

  • C++20 coroutines, concepts, and ranges

  • Basic concurrent programming

Compiler Support

  • GCC 12+

  • Clang 17+

  • Apple-Clang (macOS 14+)

  • MSVC 14.34+

  • MinGW

Dependencies

None. Capy is self-contained and does not require Boost.

Linking

Capy is a compiled library. Link against capy.

Code Convention

Unless otherwise specified, all code examples in this documentation assume the following:

#include <boost/capy.hpp>
using namespace boost::capy;

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