diff --git a/doc/antora.yml b/doc/antora.yml index f06147b2b..32798ad2d 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -18,6 +18,7 @@ asciidoc: table-caption: false page-toc: '' toclevels: 2 + icons: font nav: - modules/ROOT/nav.adoc ext: diff --git a/doc/modules/ROOT/pages/4.coroutines/4a.tasks.adoc b/doc/modules/ROOT/pages/4.coroutines/4a.tasks.adoc index fa34a5612..baab2333f 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4a.tasks.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4a.tasks.adoc @@ -17,6 +17,17 @@ include::example$snippets/4a_tasks.cpp[tag=include_task] include::example$snippets/4a_tasks.cpp[tag=include_umbrella] ---- +Unless otherwise specified, all code examples in this documentation assume the following: + +[source,cpp] +---- +include::example$snippets/index_page.cpp[tag=convention] +---- + +The examples deliberately leave some results and bindings unused, so the +surrounding prose can explain them. Do not build them with `-Wall` or +`-Werror`. + ==== == Overview diff --git a/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc b/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc index 9793b7d35..f5d0a3264 100644 --- a/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc +++ b/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc @@ -32,8 +32,8 @@ NOTE: Reaching the end of stream is also a contingency (which can be interpreted as preventing an infinite read from proceeding). -NOTE: The stream operations can still throw exceptions to indicate conditions - unrelated to stream state that prevent these operations from satisfying +NOTE: The stream operations can still throw exceptions to indicate conditions, + unrelated to stream state, that prevent these operations from satisfying their postconditions, such as failures to grow a buffer, or failure to allocate a coroutine frame. diff --git a/doc/modules/ROOT/pages/index.adoc b/doc/modules/ROOT/pages/index.adoc index 84556564c..bc5af2cfb 100644 --- a/doc/modules/ROOT/pages/index.adoc +++ b/doc/modules/ROOT/pages/index.adoc @@ -1,110 +1,36 @@ = Capy :page-mode: explanation -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. +Capy is a compiled C++20 library providing abstractions, vocabulary types, and idioms necessary +to write programs with event-driven control flows +-- such as I/O -- in a way that makes them efficient and manageable: structured and hard to get wrong. -== What Capy Is +Coroutines are used to represent the control flow. The program logic is described in a sequential way, +familiar to programmers, +even though there are tasks being pushed to task queues behind the scenes. -Capy is two things at once: +I/O operations are represented via the concept of a _stream_ which consumes and populates _buffer sequences_. +You also get type-erased wrappers for streams, so that programs can compile quickly, without triggering +excessive template instantiations. -* *A protocol.* cpp: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. +It offers a number of task synchronization mechanisms, ranging from low-level, like async mutexes and events, +to high-level, like _strands_, `when_all`/`when_any` "joins", and the resume-on-the-same-executor guarantee. -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. +Libraries that want to provide concrete I/O implementations -- like those based on `epoll` or `io_uring` -- +can plug into Capy's system via the `IoAwaitable` protocol, which describes how tasks can be scheduled, +cancelled, and allocated in memory. -[IMPORTANT] -==== -*The core invariant: a coroutine always resumes on the executor it was started with.* +You also get the testing tools that allow you to test your asynchronous flows in a deterministic way. -Start 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. Capy therefore rejects it at compile time, and provides an explicit way to bridge such awaitables when you need one. -See xref:4.coroutines/4c.executors.adoc#the-same-executor-invariant[the same-executor invariant] for the rationale and xref:4.coroutines/4d.io-awaitable.adoc#bridging-a-foreign-awaitable[bridging a foreign awaitable] for the escape hatch. -==== +== What Capy is Not -== What Capy Is Not +While Capy offers algorithms and interfaces for dealing with I/O, +it does not itself provide any I/O backend (like a wrapper over `epoll` or `io_uring`). +For that, you will need to use Capy in tandem with a Capy-conformant library. +This can be either your home-grown one or Corosio. -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. It works standalone for logic that operates on streams without any platform I/O (HTTP parsing, protocol state machines, serialization). It also serves 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* — cpp:task[task] with forward-propagating stop tokens and automatic cancellation -* *Buffer sequences* — taken straight from Asio and improved -* *Stream concepts* — three coroutine stream concepts: cpp:ReadStream[], cpp:WriteStream[], cpp:Stream[] -* *Type-erased streams* — cpp:any_stream[], cpp:any_read_stream[], cpp:any_write_stream[] for fast compilation -* *Concurrency facilities* — executors, strands, thread pools, cpp:when_all[], cpp:when_any[] -* *Test utilities* — mock streams, 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 - -== The Library Family - -Capy is the foundation of a family of coroutine libraries. Each builds on Capy's execution model and byte streams to add a layer of the networking stack. The sibling libraries are in active development; their repositories may be incomplete or not yet released. - -* *Capy* — execution model, buffer sequences, and byte streams (this library) -* https://github.com/cppalliance/corosio[Corosio] — portable coroutine networking _(in development)_ -* https://github.com/cppalliance/http[Http] — sans-I/O HTTP/1.1 clients and servers _(in development)_ -* https://github.com/cppalliance/websocket[Websocket] — sans-I/O WebSocket _(in development)_ -* https://github.com/cppalliance/beast2[Beast2] — high-level HTTP/WebSocket servers _(in development)_ -* https://github.com/cppalliance/burl[Burl] — high-level HTTP client _(in development)_ - -== 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 {cpp}.* Type erasure at boundaries, minimal dependencies, and hidden implementations keep builds fast and templates manageable. - -== Requirements - -=== Assumed Knowledge - -* {cpp}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 `Boost::capy`. - -== Code Convention - -[NOTE] -==== -Unless otherwise specified, all code examples in this documentation assume the following: - -[source,cpp] ----- -include::example$snippets/index_page.cpp[tag=convention] ----- - -The examples deliberately leave some results and bindings unused, so the -surrounding prose can explain them. Do not build them with `-Wall` or -`-Werror`. -==== == Quick Example @@ -112,10 +38,11 @@ surrounding prose can explain them. Do not build them with `-Wall` or ---- include::example$programs/index_page_echo.cpp[tag=full] ---- +<.> The cpp:task[task<>] return type defines a coroutine that starts suspended. `any_stream` is a type-erased wrapper that works with any concrete stream implementation. +<.> Each `co_await` suspends until the I/O operation completes. +<.> You can signal failures by throwing an exception, or by returning a `std::error_code`. +<.> The condition `cond::eof` indicates reaching the end of the stream. -The `echo` function accepts an `any_stream&`—a type-erased wrapper that works with any concrete stream implementation. Each `co_await` suspends until the I/O completes. - -The cpp:task[task<>] return type (equivalent to `task`) creates a lazy coroutine that does not start executing until awaited or started with cpp:run_async[]. == Next Steps diff --git a/doc/modules/ROOT/pages/quick-start.adoc b/doc/modules/ROOT/pages/quick-start.adoc index d310d5e9d..fe81a49f9 100644 --- a/doc/modules/ROOT/pages/quick-start.adoc +++ b/doc/modules/ROOT/pages/quick-start.adoc @@ -10,7 +10,24 @@ = Quick Start :page-mode: tutorial -NOTE: Capy requires {cpp}20 with coroutine support. +== Requirements + +=== Assumed Knowledge + +* {cpp}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. == Minimal Example @@ -38,7 +55,9 @@ g++ -std=c++20 -I/path/to/capy/include -o hello_coro hello_coro.cpp \ /path/to/capy/build/libboost_capy.a -pthread ---- -Then run it: +NOTE: If your prorgam is build with CMake, link it against `Boost::capy`. + +Then run the compiled program: [source,bash,role=external] ---- diff --git a/doc/modules/ROOT/pages/why-capy.adoc b/doc/modules/ROOT/pages/why-capy.adoc index ce5085de4..94f07c604 100644 --- a/doc/modules/ROOT/pages/why-capy.adoc +++ b/doc/modules/ROOT/pages/why-capy.adoc @@ -10,6 +10,11 @@ That restriction buys a guarantee the hybrid libraries cannot make: *a coroutine include::example$snippets/why_capy.cpp[tag=invariant] ---- +[NOTE] +==== +See xref:4.coroutines/4c.executors.adoc#the-same-executor-invariant[the same-executor invariant] for the rationale and xref:4.coroutines/4d.io-awaitable.adoc#bridging-a-foreign-awaitable[bridging a foreign awaitable] for the escape hatch. +==== + == What Capy Is Not Capy is not a networking library. It has no sockets, no acceptors, no DNS, no TLS, and no platform event loop. Those belong to Corosio, which is built on Capy. diff --git a/test/doc/programs/index_page_echo.cpp b/test/doc/programs/index_page_echo.cpp index 003dff7c1..33e45e948 100644 --- a/test/doc/programs/index_page_echo.cpp +++ b/test/doc/programs/index_page_echo.cpp @@ -15,20 +15,23 @@ namespace capy = boost::capy; -capy::task<> echo(capy::any_stream& stream) +capy::task echo(capy::any_stream& stream) // <.> { char buf[1024]; for(;;) { - auto [ec, n] = co_await stream.read_some(capy::make_buffer(buf)); + auto [ec, n] = co_await stream.read_some(capy::make_buffer(buf)); // <.> - auto [wec, wn] = co_await capy::write(stream, capy::const_buffer(buf, n)); - - if(ec) - co_return; + auto [wec, _] = co_await capy::write(stream, capy::const_buffer(buf, n)); if(wec) + throw std::system_error(wec); // <.> + + if(ec == capy::cond::eof) // <.> co_return; + + if(ec) + throw std::system_error(ec); } }