diff --git a/.github/instructions/anyhttp.instructions.md b/.github/instructions/anyhttp.instructions.md index 6d972cb..c81d2e4 100644 --- a/.github/instructions/anyhttp.instructions.md +++ b/.github/instructions/anyhttp.instructions.md @@ -10,14 +10,14 @@ Everything is asynchronous and written with C++20 coroutines. Every operation is (`read_some`, `write`, `write_eof`, `submit`, `get_response`, `connect`, `get`) that yields a tuple and reports errors instead of throwing. -There are two API styles, chosen at configure time with `-DANYHTTP_API=`: +There are two I/O runtimes, chosen at configure time with `-DANYHTTP_API=`: * **ASIO** (default), on Boost.Asio: every operation is also an ASIO asynchronous operation (`async_x(..., token)`) and takes any completion token, adhering to its [async model]. * **COROSIO**, on [capy](https://github.com/cppalliance/capy) and [corosio](https://github.com/cppalliance/corosio): coroutines only. -Both styles build the same library sources and tests; code that belongs to one of them is guarded +Both runtimes build the same library sources and tests; code that belongs to one of them is guarded with `ANYHTTP_ASIO` / `ANYHTTP_COROSIO` (exactly one is 1, from the generated `anyhttp/config.hpp`) and lives in `asio/` or `corosio/`, under both `include/anyhttp/` and `src/`. Generic code reaches the runtime only through `anyhttp/runtime.hpp`, `anyhttp/net.hpp` and `anyhttp/formatter.hpp`, and @@ -33,7 +33,7 @@ functions `snake_case`, members `foo_`. Commit subjects are prefixed `fix:`, `re `chore:`, `style:`, `test:`, or with the protocol (`h1:`, `h2:`, `h3:`). Builds are out-of-source, one tree per configuration (`build/` Debug, `build-release/` Release, -`build-corosio/` the COROSIO style, plus sanitizer trees); `scripts/configure.sh` sets them up. +`build-corosio/` the COROSIO runtime, plus sanitizer trees); `scripts/configure.sh` sets them up. Run the tests from the repository root -- they load TLS material through relative paths -- and prefer `gtest-parallel build/test/test_all`. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a44ac9c..03f0e98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ env: jobs: # - # Both API styles in all three flavors, on OpenSSL: COROSIO needs it (corosio's openssl_stream + # Both I/O runtimes in all three flavors, on OpenSSL: COROSIO needs it (corosio's openssl_stream # uses a zero-copy BIO API that AWS-LC lacks), so it is the baseline rather than the default # AWS-LC. The legs after the product each isolate one variable the product holds fixed -- the # standard library, the TLS library -- so that a red job names its own cause. COROSIO TSAN is @@ -50,7 +50,7 @@ jobs: api: [ASIO, COROSIO] flavor: [RelWithDebInfo, ASAN, TSAN] include: - # Flags per flavor, merged into the leg of each API style. + # Flags per flavor, merged into the leg of each I/O runtime. - flavor: RelWithDebInfo cmake: -DCMAKE_BUILD_TYPE=RelWithDebInfo - flavor: ASAN @@ -59,7 +59,7 @@ jobs: - flavor: TSAN cmake: -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" opts: TSAN_OPTIONS=report_signal_unsafe=0 - # Legs of their own: GCC and libstdc++ for both styles, and the other TLS library. + # Legs of their own: GCC and libstdc++ for both runtimes, and the other TLS library. - api: ASIO flavor: GCC cc: gcc diff --git a/CLAUDE.md b/CLAUDE.md index c1f0c2f..efb41cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,10 +13,10 @@ benchmark with the latter, never with `build/`. Both use clang, which builds aga the libraries in `/opt/libc++` (`USE_LIBCXX`, `LIBCXX_ROOT`); `build-gcc/` is GCC with libstdc++ and `/usr/local`. Keep `build/` on clang: clangd reads its `compile_commands.json`. -`build-corosio/` is the COROSIO API style (`-DANYHTTP_API=COROSIO -DTLS_LIBRARY=OpenSSL`, see +`build-corosio/` is the COROSIO I/O runtime (`-DANYHTTP_API=COROSIO -DTLS_LIBRARY=OpenSSL`, see [docs/corosio-port-plan.md](docs/corosio-port-plan.md)); capy and corosio are FetchContent'd at -pinned SHAs. Both styles build the same library sources and test files; what belongs to one style -only is guarded with `ANYHTTP_ASIO` or `ANYHTTP_COROSIO` (exactly one of them is 1). So is the +pinned SHAs. Both runtimes build the same library sources and test files; what belongs to one +runtime only is guarded with `ANYHTTP_ASIO` or `ANYHTTP_COROSIO` (exactly one of them is 1). So is the `server` program; the `client` program is ASIO-only so far. `build-corosio-asan/` is its ASAN tree, `build-corosio-release/` its Release tree (benchmark COROSIO with it) and `build-corosio-gcc/` its GCC one -- capy and corosio do build against libstdc++, but GCC is @@ -137,10 +137,9 @@ buffer (see `WriteMode`), and `build-h2v2-asan/` is the version 2 ASAN tree for Each protocol lives in a directory of its own, `h1/` (Beast/HTTP/1.1), `h2/` (nghttp2), `quic/` (ngtcp2) and `h3/` (nghttp3, on top of `quic/`), in both `include/anyhttp/` and `src/`. **Only files in `h2/` may include ``, only files in `h2v2/` ``, only files in -`quic/` and `h3/` ``, only files in `h3/` ``.** What belongs to one API style -only -is in `asio/` or `corosio/`, again in both; the library compiles the `src/` directory of its style -and not the other. `src/apps/` has the `server` and `client` programs, `src/issues/` standalone +`quic/` and `h3/` ``, only files in `h3/` ``.** What belongs to one async +runtime only is in `asio/` or `corosio/`, again in both; the library compiles the `src/` directory +of its runtime and not the other. `src/apps/` has the `server` and `client` programs, `src/issues/` standalone reproducers of upstream bugs. Everything else at the top level is generic. What gets installed is the public headers, the ones reachable from `client.hpp`, `server.hpp`, @@ -194,7 +193,7 @@ may: the h2c upgrade and formatter tests drive nghttp2 by hand.) **Generic and protocol code include nothing from Boost.Asio but ``**, which Beast's parser, serializer and `Fields` bring anyway, and nothing from capy or corosio. They reach the runtime through `anyhttp/runtime.hpp`, `anyhttp/net.hpp` and `anyhttp/formatter.hpp`, which -include the `asio/` or `corosio/` header of the build's style. Addresses and endpoints are the +include the `asio/` or `corosio/` header of the build's runtime. Addresses and endpoints are the runtime's own (`IpAddress`, `TcpEndpoint`, `UdpEndpoint`); where the two differ, `net.hpp` declares a function each runtime defines (`io::make_address()`, `io::to_sockaddr()`, ...). Verify in a COROSIO tree that no library object includes more of Asio: @@ -204,7 +203,7 @@ ninja -C build-corosio -t deps | awk '/^[^ ].*: #deps/{o=$1} /boost\/asio\// && ``` It must print nothing. (Test objects may: `test_external.cpp` runs its child processes on an ASIO -context in both styles.) +context on both runtimes.) QUIC and HTTP/3 are two layers, and each shares one implementation between server and client. `quic/session.*` has all ngtcp2 callbacks, packet writing, timers and connection flow control, @@ -222,16 +221,16 @@ shared files. ## Conventions - clang-format is authoritative: 3-space indent, 100 columns. -- The two API styles are ASIO and COROSIO: `ANYHTTP_ASIO`/`ANYHTTP_COROSIO`, +- The two I/O runtimes are ASIO and COROSIO: `ANYHTTP_ASIO`/`ANYHTTP_COROSIO`, `asio/`/`corosio/`, `build-corosio/`. "capy" names only the library itself (`capy::task`, ``), the non-I/O base that corosio adds I/O to. -- Where the two styles differ by more than a few lines, each gets a file of its own, of the same +- Where the two runtimes differ by more than a few lines, each gets a file of its own, of the same name in `asio/` and `corosio/`, with the same definitions in the same order, so that the pair can be read side by side: `runtime.hpp`, `net.hpp`, `src/*/net.cpp`. What both share stays in the generic file of that name (`anyhttp/runtime.hpp`, which selects one of the two, `anyhttp/net.hpp`, `src/net.cpp`). The test fixtures follow the old pattern, `test/test_fixtures_*.hpp`. Keep the order when adding to either. -- The style comes from the generated `anyhttp/config.hpp` (`cmake/config.hpp.in`), not from a +- The runtime comes from the generated `anyhttp/config.hpp` (`cmake/config.hpp.in`), not from a compile definition: a file that tests `ANYHTTP_ASIO`/`ANYHTTP_COROSIO` must include it (or `anyhttp/runtime.hpp`) first, or the test is silently false. - Test `.cpp` files put `using namespace testing;` after the includes and use `HasSubstr`, @@ -271,7 +270,7 @@ After adding a dependency, check `ldd` of an AWS-LC build shows no `libssl.so.3` `libcrypto.so.3` -- a shared OpenSSL would interpose the executable's AWS-LC symbols. `curl`, `osslclient` and `osslserver` are OpenSSL builds and are useful for interop testing. -**A context has to run dry, in both styles.** This is anyhttp's doing, not capy's any more. Since +**A context has to run dry, on both runtimes.** This is anyhttp's doing, not capy's any more. Since capy `8e1244f` and corosio `232c79f`, destroying an `io_context` unwinds the coroutines suspended on it, provided the continuation they are parked with carries `env->owner`: whatever in `corosio/runtime.hpp` parks or queues a coroutine has to set it. What that cannot reach is a cycle diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f04de7..a4e3c92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,11 +133,11 @@ endif() # -------------------------------------------------------------------------------------------------- # -# API style and I/O runtime, one per build tree: ASIO (completion tokens, on Boost.Asio) or COROSIO +# The I/O runtime, one per build tree: ASIO (completion tokens, on Boost.Asio) or COROSIO # (coroutines only, on capy/corosio). The code tests ANYHTTP_ASIO or ANYHTTP_COROSIO, exactly one of # which is 1, from the generated anyhttp/config.hpp. See docs/corosio-port-plan.md. # -set(ANYHTTP_API ASIO CACHE STRING "API style and I/O runtime: ASIO or COROSIO") +set(ANYHTTP_API ASIO CACHE STRING "I/O runtime: ASIO or COROSIO") set_property(CACHE ANYHTTP_API PROPERTY STRINGS ASIO COROSIO) if (ANYHTTP_API STREQUAL "ASIO") set(ANYHTTP_ASIO 1) @@ -170,7 +170,7 @@ elseif (ANYHTTP_API STREQUAL "COROSIO") GIT_TAG 41d0de18b6e4fa1b397c38a866f2daf52c6c36b5 EXCLUDE_FROM_ALL) FetchContent_MakeAvailable(capy corosio) - message(STATUS "Using API style: COROSIO (capy/corosio)") + message(STATUS "Using I/O runtime: COROSIO (capy/corosio)") else() message(FATAL_ERROR "ANYHTTP_API must be ASIO or COROSIO, not '${ANYHTTP_API}'") endif() diff --git a/CMakePresets.json b/CMakePresets.json index 2216823..5698a75 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -42,7 +42,7 @@ { "name": "corosio", "hidden": true, - "description": "The COROSIO API style, on capy/corosio, which needs OpenSSL", + "description": "The COROSIO I/O runtime, on capy/corosio, which needs OpenSSL", "inherits": "openssl", "cacheVariables": { "ANYHTTP_API": "COROSIO" @@ -177,7 +177,7 @@ { "name": "build-corosio", "displayName": "COROSIO", - "description": "The COROSIO API style, see docs/corosio-port-plan.md; capy and corosio are FetchContent'd at pinned SHAs", + "description": "The COROSIO I/O runtime, see docs/corosio-port-plan.md; capy and corosio are FetchContent'd at pinned SHAs", "inherits": [ "clang", "corosio", diff --git a/README.md b/README.md index 4fd12be..07e8903 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Documentation](https://img.shields.io/badge/documentation-read-blue)](https://pgit.github.io/anyhttp/) [![Coverage](https://img.shields.io/badge/coverage-report-blue)](https://pgit.github.io/anyhttp/coverage/) -Low-level C++23 HTTP client and server library, in one of two API styles, chosen at configure time: +Low-level C++23 HTTP client and server library, on one of two I/O runtimes, chosen at configure time: * **ASIO**, on [Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html): every operation is an asynchronous operation of Asio and takes any completion token. @@ -28,7 +28,7 @@ This project started as a hand-written replacement for [nghttp2-asio](https://gi **** has the [programmer's guide](docs/programmers-guide.rst), which is the place to start, the API reference and how it all works: -[API styles](docs/api-styles.rst), [the end of a body](docs/end-of-body.rst), +[I/O runtimes](docs/io-runtimes.rst), [the end of a body](docs/end-of-body.rst), [concurrent requests](docs/concurrent-requests.rst), [moving to HTTP/3](docs/alt-svc.rst) and [the HTTP/2 library](docs/http2-library.rst). [docs/README.md](docs/README.md) says how to make it. @@ -78,7 +78,7 @@ Task do_session(client::Client& client, boost::urls::url url) } ``` -Both API styles have every operation as such a coroutine, that yields a tuple and reports errors +Both I/O runtimes have every operation as such a coroutine, that yields a tuple and reports errors instead of throwing. On ASIO, each is also an asynchronous operation that takes any completion token. The whole programs are [hello_server.cpp](docs/examples/hello_server.cpp) and [hello_client.cpp](docs/examples/hello_client.cpp). diff --git a/cmake/config.hpp.in b/cmake/config.hpp.in index fec37b8..3410187 100644 --- a/cmake/config.hpp.in +++ b/cmake/config.hpp.in @@ -1,7 +1,7 @@ #pragma once // -// Generated by CMake from cmake/config.hpp.in: the API style this library was built for (see +// Generated by CMake from cmake/config.hpp.in: the I/O runtime this library was built for (see // anyhttp/runtime.hpp). Code using the library has to see the same one, so it comes with the // headers instead of a compile definition. // diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 4fe6a0e..9ed63c4 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(hello_client PRIVATE anyhttp) # cmake --build build --target docs # # Doxygen does not compile the headers, so unlike the snippets the documentation depends on neither -# the API style of the tree nor on what that style needs. See Doxyfile.in. +# the I/O runtime of the tree nor on what that runtime needs. See Doxyfile.in. # find_program(DOXYGEN_EXECUTABLE doxygen DOC "Doxygen, which reads the comments in the headers") find_program(SPHINX_EXECUTABLE sphinx-build DOC "Sphinx, which makes the documentation") diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 1fd2a8b..07ec5e4 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -8,7 +8,7 @@ STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/include # # The public interface, and nothing else. The comments on the vocabulary of the two runtimes are in -# asio/runtime.hpp, and corosio/runtime.hpp has the same names in the same order -- api-styles.rst +# asio/runtime.hpp, and corosio/runtime.hpp has the same names in the same order -- io-runtimes.rst # has the table of what they map to. What these headers have for the library's own use is between # \cond and \endcond. # @@ -33,7 +33,7 @@ JAVADOC_AUTOBRIEF = YES # Doxygen does not compile anything, so what a compiler would get from config.hpp and from Asio's # headers is said here -- which is also why the reference takes neither Boost nor a configured -# runtime, and comes out the same from a tree of either style +# runtime, and comes out the same from a tree of either runtime MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = ANYHTTP_ASIO=1 \ diff --git a/docs/README.md b/docs/README.md index 24ef562..96642b7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,7 +31,7 @@ apt-get install doxygen python3-sphinx python3-breathe python3-sphinx-rtd-theme ``` The target is not part of a regular build, and not there at all where CMake finds no `doxygen` or no -`sphinx-build`, or was told `ANYHTTP_BUILD_DOCS=OFF`. It can be made from a tree of either API style +`sphinx-build`, or was told `ANYHTTP_BUILD_DOCS=OFF`. It can be made from a tree of either I/O runtime and gives the same result: the comments on the types of the two runtimes are in `asio/runtime.hpp`. ## Writing it @@ -43,12 +43,12 @@ Warnings are errors in both tools, so a public symbol without a comment, a param What a public header has for the library's own use goes between `\cond internal` and `\endcond`. * In a comment, Doxygen takes `http::...` and `https://` for a link, and `\c` of either, or of a word that starts with `[`, comes out empty, which neither tool warns about. So write `errors::...`, as - that is the spelling that holds in both API styles anyway, and `[...]`. + that is the spelling that holds on both I/O runtimes anyway, and `[...]`. * Code in the guide comes from `snippets.cpp`, between a `// [name]` and a `// [name-end]` comment, and is shown with `literalinclude`. Only pseudo code is written in place. * In a page, names link to the reference with `` :cpp:func:`Session::submit` `` and the like, and files of the repository with `` :src:`src/server.cpp` ``. -* In prose, the libraries are Asio, Corosio and Capy. `ASIO` and `COROSIO` are the API styles, the +* In prose, the libraries are Asio, Corosio and Capy. `ASIO` and `COROSIO` are the I/O runtimes, the values of `ANYHTTP_API`: "an ASIO build". Capy is named only where Capy itself is meant. [`Doxyfile.in`](Doxyfile.in) configures Doxygen and [`conf.py`](conf.py) configures Sphinx. diff --git a/docs/benchmark.md b/docs/benchmark.md index 7af61c5..bd9415a 100644 --- a/docs/benchmark.md +++ b/docs/benchmark.md @@ -107,7 +107,7 @@ The tables below use `-c 32` unless they say otherwise. ## HTTP/2: nghttpd vs anyhttp *2026-10-07, measured at `d445bdf`.* nghttpd, which `bench.sh` runs as the HTTP/2 reference, -serves twice as many requests as either API style: `bench.sh -D 5 -c 32` gave 555k req/s against +serves twice as many requests as either I/O runtime: `bench.sh -D 5 -c 32` gave 555k req/s against 284k (ASIO) and 273k (COROSIO), on one thread each, with the same nghttp2 and OpenSSL. This is where the time goes; [Done since](#done-since) lists what has been fixed. @@ -144,7 +144,7 @@ instructions may sit in a neighbouring row: COROSIO, by `perf` share of user time: malloc/free 21%, `std::format` 13%, capy/corosio 12%, nghttp2 15%, Boost.URL 5%, Beast 3.5%. The same shape, so the cost lies in the generic code and -the HTTP/2 backend, not in the API style. +the HTTP/2 backend, not in the I/O runtime. ### Where it goes @@ -304,7 +304,7 @@ Scaling across independent contexts, h2 (`h2load -D 6 -c 32 -m 10 -t 4`, req/s): - **COROSIO's threaded scheduler is far better than ASIO's shared one,** but a context per thread takes most of its lead away. In the independent model COROSIO stays ahead on h1 only and is 7-22% behind on h2 and h3. -- **h1 and h3 gain from independent contexts in both styles.** COROSIO h1 drops from 26 to 15.5 µs +- **h1 and h3 gain from independent contexts on both runtimes.** COROSIO h1 drops from 26 to 15.5 µs per request, 4.7 µs of the saving in system time. - **COROSIO h2 gets slower** with independent contexts: 6.2 instead of 5.0 µs per request, with system time up by half. Load is spread evenly (all threads equally busy), and @@ -353,7 +353,7 @@ causes, both fixed: completed inside the initiating call, which `Response::submit()` always does. HTTP/3's write pass, posted by `wake_write()`, then ran first and sent each response head in a packet of its own, before the handler could add the body: twice the flushes, packets and ACKs of ASIO. Found - by counting events per request in both styles, not by profiling. Fixed by resuming at once, + by counting events per request on both runtimes, not by profiling. Fixed by resuming at once, after the initiating call has returned (`6c3c50f`): h3 97k → 143k (ASIO 124k), h2 197k → 247k (ASIO 207k), h2 cleartext 229k → 276k (ASIO 191k), with `bench.sh -D 3`. diff --git a/docs/corosio-port-plan.md b/docs/corosio-port-plan.md index 50e5edb..517d462 100644 --- a/docs/corosio-port-plan.md +++ b/docs/corosio-port-plan.md @@ -176,7 +176,7 @@ the h3 write path also run under ASAN. From step 5 on, COROSIO's own tests must awaitable returned from a wrapper that made the buffer a temporary reads into freed memory (`EFAULT` from `recv()`), so the COROSIO `io::receive()` is a coroutine. 6. **The test suite in both modes** (§5). - *Done 2026-10-01 (`012e0a5`..`86ef515`).* Every test file builds in both styles, and so does + *Done 2026-10-01 (`012e0a5`..`86ef515`).* Every test file builds on both runtimes, and so does every library source; the explicit COROSIO lists gave way to the globs. COROSIO runs 403 tests, ASIO 396, with the same 23 skips. The tests use the coroutine spelling plus a few helpers: - `check()` unwraps a result and throws its error, as ASIO's default token does. @@ -210,10 +210,10 @@ the h3 write path also run under ASAN. From step 5 on, COROSIO's own tests must *No longer capy's doing, since capy `8e1244f` and corosio `232c79f`:* destroying a context unwinds what is suspended on it, once `Initiation` and `YieldNow` carry `env->owner`. The rule stays for a different reason, and for ASIO just the same: HTTP/2 and HTTP/3 leak without the - drain in both styles, through cycles that only running can break (`do_session()` waiting on an + drain on both runtimes, through cycles that only running can break (`do_session()` waiting on an event of the session its handler keeps alive, a frame holding the `Session` or `Request` whose `Completion` it waits on). HTTP/1.1 does not. -7. **README**: the two API styles, the per-mode error table, the constraints (OpenSSL). The error table includes the resolver: corosio reports a host that does not resolve +7. **README**: the two I/O runtimes, the per-mode error table, the constraints (OpenSSL). The error table includes the resolver: corosio reports a host that does not resolve as `no_such_device_or_address` (`EAI_NONAME` mapped onto a generic code), ASIO as `netdb_errors::host_not_found`. 8. Later: threads in COROSIO; `client_main` for COROSIO; upstream issues for corosio @@ -224,14 +224,14 @@ the h3 write path also run under ASAN. From step 5 on, COROSIO's own tests must uploading when the server responds and closes loses that response, which ASIO does not. `ClientAsync`'s 404 test and four of `ClientAsyncCancellation` fail for that reason with `ANYHTTP_TEST_SCHEME=https`, and CI leaves them out there). - *`server_main` done 2026-10-01:* it builds in both styles and refuses `--threads` above 1 with + *`server_main` done 2026-10-01:* it builds on both runtimes and refuses `--threads` above 1 with COROSIO. The Debug `run()` wrapper steps a corosio context with `run_one()` too, and the COROSIO fixtures run through it. That reordering flipped an h2 test: a write issued after the stream closed reported `canceled`, one parked when it closed `connection_reset`; both report the latter now (`ccb30df`). - *Threads in COROSIO done 2026-10-03.* `server --threads N` and `Config::use_strand` work in both - styles. corosio resumes a coroutine after I/O through the executor in its `await_context` (capy's + *Threads in COROSIO done 2026-10-03.* `server --threads N` and `Config::use_strand` work on both + runtimes. corosio resumes a coroutine after I/O through the executor in its `await_context` (capy's executor affinity), so a connection launched on a strand stays on it, and so does everything it awaits. What changed: - `new_strand()` returns a `capy::strand` on the context's executor, wrapped in a forwarding @@ -244,8 +244,8 @@ the h3 write path also run under ASAN. From step 5 on, COROSIO's own tests must into the launching frame, and Readers and Writers keep the executor after they detach. - `Signal` is capy's `async_waker` (`notify()` from any thread). Its waiter must run on a strand, so the accept loop has one of its own with `use_strand`. - - `Server::Impl` destroys each session on the session's executor (`dispatch_to()`), in both - styles: `shutdown()` from the signal handler's thread raced the strand closing the socket. + - `Server::Impl` destroys each session on the session's executor (`dispatch_to()`), on both + runtimes: `shutdown()` from the signal handler's thread raced the strand closing the socket. ASIO had the same race; it never ran threaded under TSAN. - `Timer` checks its stop token once its wait has resumed: an expired wait may still be queued behind the strand when the timer is re-armed or cancelled. @@ -288,8 +288,8 @@ the h3 write path also run under ASAN. From step 5 on, COROSIO's own tests must (no inline budget); these numbers are with that default. 9. **Source layout and the Asio boundary** (2026-10-05). The protocols live in `h1/`, `h2/` and - `h3/`, and what belongs to one API style lives in `asio/` or `corosio/`, in both `include/anyhttp/` - and `src/`. The style comes from the generated `anyhttp/config.hpp` (`ab7b236`). Generic and + `h3/`, and what belongs to one I/O runtime lives in `asio/` or `corosio/`, in both `include/anyhttp/` + and `src/`. The runtime comes from the generated `anyhttp/config.hpp` (`ab7b236`). Generic and protocol code include nothing of Asio's but what Beast's parser, serializer and `Fields` bring (`boost/asio/buffer.hpp`), see CLAUDE.md for the check (`1c868ad`). Addresses and endpoints are the runtime's own: `IpAddress`, `TcpEndpoint` and `UdpEndpoint` are Boost.Asio's or diff --git a/docs/design.rst b/docs/design.rst index dba6985..f6c53ac 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -41,7 +41,7 @@ ngtcp2 and nghttp3 from being compile-time dependencies of anyone who uses anyht - HTTP/3 - nghttp3, and ``quic/`` * - ``asio/``, ``corosio/`` - - the runtime, see :doc:`api-styles` + - the runtime, see :doc:`io-runtimes` - Asio, or Corosio and Capy Generic code (``server_impl.*``, ``client_impl.*``) reaches a backend only through diff --git a/docs/index.rst b/docs/index.rst index 999ebbf..9077c84 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,7 +34,7 @@ report `__. :maxdepth: 1 :caption: How it works - api-styles + io-runtimes end-of-body concurrent-requests alt-svc diff --git a/docs/api-styles.rst b/docs/io-runtimes.rst similarity index 57% rename from docs/api-styles.rst rename to docs/io-runtimes.rst index a131e35..1414e35 100644 --- a/docs/api-styles.rst +++ b/docs/io-runtimes.rst @@ -1,20 +1,24 @@ .. cpp:namespace:: anyhttp -API styles -========== +I/O runtimes: Asio and Corosio +============================== -A build of the library is of one of two API styles, chosen when it is configured: +A build of the library is on one of two I/O runtimes, chosen when it is configured: * **ASIO**, on `Asio `__: every operation is an asynchronous operation of Asio, and takes any completion token. It is also there as a coroutine. * **COROSIO**, on `Corosio `__: coroutines only. -Both styles build the same library and protocol sources. Only a thin runtime layer differs, +Each brings the sockets, timers and TLS, on top of an async runtime: the executors, coroutines and +cancellation. Asio is both in one. Corosio is the I/O only, on top of `Capy +`__, which is the async runtime. + +Both build the same library and protocol sources. Only a thin runtime layer differs, ``anyhttp/runtime.hpp`` and ``anyhttp/net.hpp`` with what they include from ``asio/`` or ``corosio/``. :src:`docs/corosio-port-plan.md` has how that came about. -.. _api-styles-vocabulary: +.. _io-runtimes-vocabulary: What the names stand for ------------------------ @@ -51,16 +55,56 @@ What the names stand for - ``server``, ``client`` - ``server`` -Both styles have every operation as a coroutine, ``read_some``, ``write``, ``write_eof``, ``submit``, -``get_response``, ``connect``, ``get``, that yields a tuple and reports errors instead of throwing. -Code written like that builds with either, which is what the guide does. Buffers are Asio's in both, -headers are Beast's :cpp:type:`Fields`, URLs Boost.URL's. +Buffers are Asio's on both, headers are Beast's :cpp:type:`Fields`, URLs Boost.URL's. The reference shows what the headers have for ASIO, as Doxygen is told to read them that way. So the reference has the operations with completion tokens that a COROSIO build does not have, and the types as they are in the table. ``corosio/runtime.hpp`` has the same names as ``asio/runtime.hpp``, in the same order. +.. _io-runtimes-operations: + +The shape of an operation +------------------------- + +On ASIO, an operation comes in two shapes. The asynchronous operation of Asio is named ``async_...`` +and takes a completion token last, which decides what it returns, and the coroutine of the plain name +wraps it. On COROSIO, the coroutine is all there is. Taking :cpp:func:`Reader::read_some` for an +example, with the types of the library: + +.. code-block:: cpp + + // ASIO: the operation, which completes a handler of the signature void(error_code, size_t) ... + template + auto async_read_some(asio::mutable_buffer buffer, CompletionToken&& token = CompletionToken()); + + // ... and the coroutine around it. + Task> read_some(asio::mutable_buffer buffer); + + // COROSIO: the coroutine. + Task> read_some(asio::mutable_buffer buffer); + +The coroutine has the same declaration on both, so code that uses only coroutines builds with +either, which is what the guide does. It yields a tuple with the error first, and reports what goes +wrong instead of throwing it. Every operation has it: ``read_some``, ``write``, ``write_eof``, +``submit``, ``get_response``, ``connect``, ``get``, and the others. What it does not have is a +completion token, so an ASIO build that wants one, for a callback, for ``cancel_after`` or to have +errors thrown, uses the ``async_...`` shape, see :ref:`completion-tokens` in the guide. + +With the types spelled out, the same piece of code on ASIO, using both shapes: + +.. literalinclude:: snippets.cpp + :language: cpp + :start-after: // [shape-asio] + :end-before: // [shape-asio-end] + +And on COROSIO: + +.. literalinclude:: snippets.cpp + :language: cpp + :start-after: // [shape-corosio] + :end-before: // [shape-corosio-end] + On ASIO, the implementation of each operation is hidden behind `any_completion_handler `__, so that it can be compiled separately. On COROSIO, they are coroutines of `Capy @@ -68,7 +112,7 @@ so that it can be compiled separately. On COROSIO, they are coroutines of `Capy ``std::stop_token``. :cpp:class:`Reader` and :cpp:class:`Writer` model ``capy::ReadStream`` and ``capy::WriteStream``. -.. _api-styles-errors: +.. _io-runtimes-errors: What the errors are ------------------- diff --git a/docs/notes.rst b/docs/notes.rst index de6611d..266a501 100644 --- a/docs/notes.rst +++ b/docs/notes.rst @@ -6,7 +6,7 @@ rather than documentation of how it is. So they say what was true when they were * :src:`docs/benchmark.md`: how to measure, the tools that work on the development host, and what has been measured so far. -* :src:`docs/corosio-port-plan.md`: how the COROSIO API style came about, step by step, and what is +* :src:`docs/corosio-port-plan.md`: how the COROSIO runtime came about, step by step, and what is left of it. * :src:`docs/capy-corosio-lessons.md`: what the port taught about Capy and Corosio. * :src:`docs/quic-streams-plan.md`: how plain QUIC streams got separated from HTTP/3. diff --git a/docs/programmers-guide.rst b/docs/programmers-guide.rst index 4cf963f..a6d16cf 100644 --- a/docs/programmers-guide.rst +++ b/docs/programmers-guide.rst @@ -6,7 +6,7 @@ Programmer's guide This goes through what a program does with the library, from its first request to shutting down. The code is taken from :src:`docs/examples` and :src:`docs/snippets.cpp`, which are compiled with every build, and :src:`test/test_docs.cpp` runs it over all three protocols. What is about how it -all works rather than how it is used has a page of its own, starting with :doc:`api-styles`. +all works rather than how it is used has a page of its own, starting with :doc:`io-runtimes`. .. contents:: On this page :local: @@ -20,7 +20,7 @@ Dockerfiles in :src:`.devcontainer` build all of them, and those are the version see :doc:`devcontainer`. Whatever else goes into the process has to agree with anyhttp on the standard library and the TLS library. -As a subproject, which builds the library and nothing else, in both API styles: +As a subproject, which builds the library and nothing else, on both I/O runtimes: .. code-block:: cmake @@ -29,7 +29,7 @@ As a subproject, which builds the library and nothing else, in both API styles: GIT_REPOSITORY https://github.com/pgit/anyhttp.git GIT_TAG SYSTEM) - set(TLS_LIBRARY OpenSSL) # the options of the API style and the libraries, if the defaults won't do + set(TLS_LIBRARY OpenSSL) # the options of the runtime and the libraries, if the defaults won't do FetchContent_MakeAvailable(anyhttp) target_link_libraries(my_program PRIVATE anyhttp::anyhttp) @@ -38,7 +38,7 @@ As a subproject, which builds the library and nothing else, in both API styles: default to ``OFF`` that way. To develop against a checkout, set ``FETCHCONTENT_SOURCE_DIR_ANYHTTP`` to it. -Or installed, which is for the ASIO style only: Corosio writes install rules only when it is the +Or installed, which is for the ASIO runtime only: Corosio writes install rules only when it is the top-level project, so a library that links it can't be exported. .. code-block:: sh @@ -56,8 +56,8 @@ top-level project, so a library that links it can't be exported. built with. The package has the public headers only, those of the protocol backends stay behind. It looks for its dependencies where they were when it was built. -A build is of one API style or the other, ``-DANYHTTP_API=ASIO``, the default, or ``COROSIO``, which -also takes ``-DTLS_LIBRARY=OpenSSL``. :doc:`api-styles` has the difference. Everything below uses +A build is on one I/O runtime or the other, ``-DANYHTTP_API=ASIO``, the default, or ``COROSIO``, which +also takes ``-DTLS_LIBRARY=OpenSSL``. :doc:`io-runtimes` has the difference. Everything below uses what both have and builds with either, and what is for ASIO only says so. A first server @@ -103,7 +103,7 @@ The whole of a program, which makes one GET and prints what comes back: A :cpp:class:`client::Client` makes connections, and :cpp:func:`connect() ` gives each as a :cpp:class:`Session`, which is what requests are submitted on. There is no error to -catch: like every operation that both API styles have, these yield a tuple with the error first, and +catch: like every operation that both runtimes have, these yield a tuple with the error first, and report what goes wrong instead of throwing it. The library has something to say about it all, too, which goes to the default logger of spdlog, see `Logging`_. @@ -312,7 +312,7 @@ Errors ------ What goes wrong is an :cpp:type:`error_code`. Where the two runtimes spell one differently, the -namespace ``errors::`` names it, see :ref:`the table `, and anything else compares +namespace ``errors::`` names it, see :ref:`the table `, and anything else compares against ``errc::``: ``ec == errc::connection_reset``. The ones that belong to the contract of an operation are in its reference and in :doc:`end-of-body` and :doc:`concurrent-requests`. @@ -326,7 +326,7 @@ abort, it also ends the connection, so a handler that means to go on has to catc Cancellation ------------ -How an operation is cancelled depends on the API style: with a cancellation slot, from a completion +How an operation is cancelled depends on the I/O runtime: with a cancellation slot, from a completion token, or through the ``std::stop_token`` of the awaiting coroutine. What works in both is racing it against something else, with :cpp:func:`when_either`, which cancels the loser: @@ -352,6 +352,8 @@ depends on how far it had come, and on the protocol: A coroutine that has been cancelled and wants to clean up by awaiting something else, as a last ``write_eof()``, needs :cpp:func:`reset_cancellation` and :cpp:func:`shielded` to get there. +.. _completion-tokens: + Completion tokens ----------------- @@ -378,7 +380,7 @@ A callback is a token as well: :start-after: // [token-callback] :end-before: // [token-callback-end] -So is ``cancel_after``, which is the short way to a timeout on this API style: +So is ``cancel_after``, which is the short way to a timeout on this runtime: .. literalinclude:: snippets.cpp :language: cpp @@ -417,7 +419,7 @@ that on the server's own executor, and then let the context run on until it retu :start-after: // [stop] :end-before: // [stop-end] -A context has to run dry, in both API styles. What is suspended when it is destroyed mostly gets +A context has to run dry, on both I/O runtimes. What is suspended when it is destroyed mostly gets unwound, but not what waits on an object that only comes apart by running, and sessions do. HTTP/2 and HTTP/3 leak without that drain. diff --git a/docs/quic-streams-plan.md b/docs/quic-streams-plan.md index 20d2821..b542596 100644 --- a/docs/quic-streams-plan.md +++ b/docs/quic-streams-plan.md @@ -130,7 +130,7 @@ The after-the-end and cancellation rules carry over unchanged. - **Server:** an ALPN registered with a connection handler, `Task(quic::Connection)`, on the existing `Server`, sharing its UDP socket and CID demux. -Both API styles, like the rest: coroutines in both, `async_*` with completion tokens in ASIO. +Both I/O runtimes, like the rest: coroutines in both, `async_*` with completion tokens in ASIO. --------------------------------------------------------------------------------------------------- @@ -148,7 +148,7 @@ Both API styles, like the rest: coroutines in both, `async_*` with completion to ## 6. Steps -Each step leaves both API styles building and the full suite green. +Each step leaves both I/O runtimes building and the full suite green. 1. **Transport into `quic/`, nghttp3 behind `Http3Application`.** A pure refactor that changes no behavior, with an ASAN run (CLAUDE.md: HTTP/3 write-path lifetimes) and the new boundary diff --git a/docs/reference.rst b/docs/reference.rst index 62cd150..3117f47 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -7,7 +7,7 @@ says how these go together. Everything is in the namespace ``anyhttp``, with what is for one side in ``anyhttp::client``, ``anyhttp::server`` and ``anyhttp::quic``. This shows the headers of an ASIO build: on COROSIO, there are no operations with completion tokens, and the types are the other column of -:ref:`the table `. +:ref:`the table `. Client ------ @@ -142,7 +142,7 @@ The runtime #include What the interface is written in, and what each name is on ASIO. On COROSIO the same names are -something else, which :ref:`the table ` has. +something else, which :ref:`the table ` has. .. doxygentypedef:: anyhttp::Task @@ -165,7 +165,7 @@ something else, which :ref:`the table ` has. Errors ^^^^^^ -Where the two runtimes spell an error differently, see :ref:`the table `. Anything +Where the two runtimes spell an error differently, see :ref:`the table `. Anything else compares against ``errc::``. .. doxygenvariable:: anyhttp::errors::eof diff --git a/docs/snippets.cpp b/docs/snippets.cpp index 71e33f9..3553f64 100644 --- a/docs/snippets.cpp +++ b/docs/snippets.cpp @@ -404,7 +404,44 @@ Task> quic_hello(Executor executor, std::str // ------------------------------------------------------------------------------------------------- // -// What only the ASIO style has. +// The shape of an operation on either runtime, with the types the aliases stand for. +// + +#if ANYHTTP_ASIO +// [shape-asio] +boost::asio::awaitable read_twice(Reader& reader, boost::asio::mutable_buffer buffer) +{ + // The asynchronous operation, with a completion token that reports errors instead of throwing. + auto [ec, n] = co_await reader.async_read_some(buffer, boost::asio::as_tuple); + if (ec) + co_return n; + + // The coroutine around it. + boost::asio::awaitable> next = + reader.read_some(buffer + n); + auto [next_ec, m] = co_await std::move(next); + co_return n + m; +} +// [shape-asio-end] +#else +// [shape-corosio] +boost::capy::task read_twice(Reader& reader, boost::asio::mutable_buffer buffer) +{ + // The coroutine, which is all there is. + auto [ec, n] = co_await reader.read_some(buffer); + if (ec) + co_return n; + + boost::capy::task> next = reader.read_some(buffer + n); + auto [next_ec, m] = co_await std::move(next); + co_return n + m; +} +// [shape-corosio-end] +#endif + +// ------------------------------------------------------------------------------------------------- +// +// What only the ASIO runtime has. // #if ANYHTTP_ASIO diff --git a/docs/testing.rst b/docs/testing.rst index 60affbd..170c9bf 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -55,7 +55,7 @@ A derived fixture adjusts the configuration by overriding ``configure_server()`` void configure_client(client::Config& config) override { config.max_header_size = 4_k; } }; -The fixtures themselves differ by API style, in :src:`test/test_fixtures_asio.hpp` and +The fixtures themselves differ by I/O runtime, in :src:`test/test_fixtures_asio.hpp` and :src:`test/test_fixtures_corosio.hpp`. Asynchronous tests: ``requestHandler`` and ``clientSession`` diff --git a/include/anyhttp/common.hpp b/include/anyhttp/common.hpp index 912f20a..1eae82f 100644 --- a/include/anyhttp/common.hpp +++ b/include/anyhttp/common.hpp @@ -147,7 +147,7 @@ using Status = void(error_code); using StatusHandler = Completion; #if ANYHTTP_ASIO -/// What an operation of a style that has completion tokens takes when it is given none. +/// What an operation of a runtime that has completion tokens takes when it is given none. using DefaultCompletionToken = asio::default_completion_token_t; #endif diff --git a/scripts/bench.sh b/scripts/bench.sh index 2c6160c..321ae6b 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -123,7 +123,7 @@ stop_server() { } trap stop_server EXIT -# Starts server $1 (a style or nghttpd) and waits until it listens: on TCP and UDP (on every thread +# Starts server $1 (a runtime or nghttpd) and waits until it listens: on TCP and UDP (on every thread # with -i), nghttpd on TCP. A failed bind() aborts quietly, and h2load would then measure whatever # else owns the port. start_server() { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a4b7d47..2496ba5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,7 +25,7 @@ add_compile_definitions(BOOST_PROCESS_USE_STD_FS) add_compile_definitions(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK) # -# Both API styles build every test file: one that belongs to a single style, or has parts that do, +# Both I/O runtimes build every test file: one that belongs to a single runtime, or has parts that do, # guards them with ANYHTTP_ASIO or ANYHTTP_COROSIO. # file(GLOB SRC_FILES test_*.cpp) diff --git a/test/test_corosio.cpp b/test/test_corosio.cpp index 0dbb2f0..65a3b6a 100644 --- a/test/test_corosio.cpp +++ b/test/test_corosio.cpp @@ -159,7 +159,7 @@ TEST(Corosio, BeastParserAndSerializer) // ------------------------------------------------------------------------------------------------- // -// Why each API style needs error constants of its own: a Boost.System code survives the conversion +// Why each I/O runtime needs error constants of its own: a Boost.System code survives the conversion // to std::error_code -- Beast's errors stay comparable across it -- but asio's eof does not become // capy's. What docs/end-of-body.rst calls errors::eof at the end of a body is capy::error::eof in // COROSIO.