From 483be41fabc074316194ceb93f87a3e171c28bf2 Mon Sep 17 00:00:00 2001 From: Peter Eisenlohr Date: Fri, 6 Nov 2026 17:48:17 +0000 Subject: [PATCH 1/3] refactor: name an operation's result results, its coroutine IoTask Every operation yields its error first, so std::tuple was spelled out ~100 times. results is capy's alias with the runtime's error_code (boost::system's on ASIO, capy::results itself on COROSIO), and IoTask is Task>. Task stays the plain coroutine, so Task and value tasks are unchanged. Co-Authored-By: Claude Opus 5.5 --- docs/io-runtimes.rst | 15 +++++++--- docs/reference.rst | 4 +++ docs/snippets.cpp | 12 ++++---- include/anyhttp/asio/net.hpp | 6 ++-- include/anyhttp/asio/runtime.hpp | 37 +++++++++++++++++------- include/anyhttp/client.hpp | 4 +-- include/anyhttp/client_impl.hpp | 2 +- include/anyhttp/corosio/net.hpp | 8 ++--- include/anyhttp/corosio/runtime.hpp | 23 +++++++++------ include/anyhttp/detail/detect.hpp | 2 +- include/anyhttp/h1/io.hpp | 29 ++++++++----------- include/anyhttp/h1/session.hpp | 6 ++-- include/anyhttp/h2/session.hpp | 2 +- include/anyhttp/h2/session_details.hpp | 2 +- include/anyhttp/h2v2/session.hpp | 2 +- include/anyhttp/h2v2/session_details.hpp | 2 +- include/anyhttp/quic.hpp | 7 ++--- include/anyhttp/quic/streams.hpp | 2 +- include/anyhttp/reader.hpp | 6 ++-- include/anyhttp/reader_impl.hpp | 2 +- include/anyhttp/runtime.hpp | 3 +- include/anyhttp/server.hpp | 2 +- include/anyhttp/server_impl.hpp | 2 +- include/anyhttp/session.hpp | 11 ++++--- include/anyhttp/session_impl.hpp | 5 ++-- include/anyhttp/writer.hpp | 6 ++-- include/anyhttp/writer_impl.hpp | 2 +- src/asio/net.cpp | 6 ++-- src/client.cpp | 12 ++------ src/corosio/net.cpp | 6 ++-- src/h1/session.cpp | 18 ++++++------ src/quic/client.cpp | 4 +-- src/quic/connection.cpp | 7 ++--- src/quic/streams.cpp | 2 +- src/reader.cpp | 7 ++--- src/server.cpp | 2 +- src/session.cpp | 17 +++++------ src/writer.cpp | 6 ++-- test/test_connection_end.cpp | 2 +- test/test_corosio.cpp | 20 ++++++------- test/test_fixtures.hpp | 2 +- test/test_quic.cpp | 2 +- 42 files changed, 160 insertions(+), 157 deletions(-) diff --git a/docs/io-runtimes.rst b/docs/io-runtimes.rst index 1414e35..aebb3d9 100644 --- a/docs/io-runtimes.rst +++ b/docs/io-runtimes.rst @@ -42,6 +42,12 @@ What the names stand for * - :cpp:type:`error_code` - ``boost::system::error_code`` - ``std::error_code`` + * - :cpp:type:`results\ ` + - ``std::tuple`` + - ``capy::results`` + * - :cpp:type:`IoTask\ ` + - ``asio::awaitable>`` + - ``capy::task>`` * - :cpp:type:`TcpEndpoint`, :cpp:type:`UdpEndpoint` - ``asio::ip::tcp::endpoint``, ``asio::ip::udp::endpoint`` - ``corosio::endpoint`` @@ -79,14 +85,15 @@ example, with the types of the library: auto async_read_some(asio::mutable_buffer buffer, CompletionToken&& token = CompletionToken()); // ... and the coroutine around it. - Task> read_some(asio::mutable_buffer buffer); + IoTask read_some(asio::mutable_buffer buffer); // COROSIO: the coroutine. - Task> read_some(asio::mutable_buffer buffer); + IoTask 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``, +either, which is what the guide does. It yields :cpp:type:`results`, a tuple with the error first, +and reports what goes wrong instead of throwing it; :cpp:type:`IoTask\ ` is short +for ``Task>``. 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. diff --git a/docs/reference.rst b/docs/reference.rst index 3117f47..f522226 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -156,6 +156,10 @@ something else, which :ref:`the table ` has. .. doxygentypedef:: anyhttp::error_code +.. doxygentypedef:: anyhttp::results + +.. doxygentypedef:: anyhttp::IoTask + .. doxygentypedef:: anyhttp::system_error .. doxygentypedef:: anyhttp::DefaultCompletionToken diff --git a/docs/snippets.cpp b/docs/snippets.cpp index 3553f64..91dd1d7 100644 --- a/docs/snippets.cpp +++ b/docs/snippets.cpp @@ -109,7 +109,7 @@ Task route(server::Request request, server::Response response) // // [read-all] -Task> read_all(Reader& reader) +IoTask read_all(Reader& reader) { std::string body; std::array buffer; @@ -126,8 +126,7 @@ Task> read_all(Reader& reader) // [read-all-end] // [post] -Task> post(Session& session, boost::urls::url url, - std::string body) +IoTask post(Session& session, boost::urls::url url, std::string body) { auto [submit_ec, request] = co_await session.submit(url, fields({{"Content-Length", body.size()}})); @@ -220,8 +219,7 @@ Task round_trip(Session& session, boost::urls::url url, std::string // [both-ways-end] // [would-block] -Task> submit_patiently(Session& session, - boost::urls::url url) +IoTask submit_patiently(Session& session, boost::urls::url url) { for (;;) { @@ -383,8 +381,8 @@ inline void quic_serve(server::Server& server) } // [quic-client] -Task> quic_hello(Executor executor, std::string host, - std::string port, std::string ca_file) +IoTask quic_hello(Executor executor, std::string host, std::string port, + std::string ca_file) { auto [connect_ec, connection] = co_await quic::connect( executor, std::move(host), std::move(port), {.alpn = "echo", .tls_ca_file = ca_file}); diff --git a/include/anyhttp/asio/net.hpp b/include/anyhttp/asio/net.hpp index ea6c8fb..161dd42 100644 --- a/include/anyhttp/asio/net.hpp +++ b/include/anyhttp/asio/net.hpp @@ -311,12 +311,10 @@ std::pair buffer_sizes(TcpSocket& socket); void set_buffer_sizes(TcpSocket& socket, int send, int receive); /// Resolves \p host and \p port (a number): (error_code, endpoints). -Task>> resolve(Executor executor, std::string host, - std::string port); +IoTask> resolve(Executor executor, std::string host, std::string port); /// Connects \p socket to the first of \p endpoints that takes it: (error_code, endpoint). -Task> connect(TcpSocket& socket, - std::vector endpoints); +IoTask connect(TcpSocket& socket, std::vector endpoints); inline TlsStream make_tls_stream(TcpSocket&& socket, TlsContext& context) { diff --git a/include/anyhttp/asio/runtime.hpp b/include/anyhttp/asio/runtime.hpp index 9544a16..ee6c7a3 100644 --- a/include/anyhttp/asio/runtime.hpp +++ b/include/anyhttp/asio/runtime.hpp @@ -90,6 +90,22 @@ using UdpEndpoint = asio::ip::udp::endpoint; /// What every operation reports its outcome with. using error_code = boost::system::error_code; +/** + * What an operation yields: its error first, then what it has to report besides, which + * `auto [ec, n] = co_await reader.read_some(buffer);` takes apart. Whether that is meaningful + * when there is an error is up to the operation. Capy's `capy::results` with the runtime's + * #error_code, so that on COROSIO the two are the same type. + */ +template +using results = std::tuple; + +/** + * An operation, as the coroutine that yields its #results: `IoTask` is + * `Task>`, `IoTask<>` one that reports only its error. + */ +template +using IoTask = Task>; + // ================================================================================================= /** @@ -280,7 +296,7 @@ inline void launch(const Executor& executor, Task task, F&& on_done) * other runtime has no tokens, and returns such a task's result directly. */ template -inline void launch(const Executor& executor, Task> task, +inline void launch(const Executor& executor, IoTask task, Completion&& handler) { auto slot = asio::get_associated_cancellation_slot(handler); @@ -292,15 +308,14 @@ inline void launch(const Executor& executor, Task> // asio::co_spawn(executor, std::move(task), asio::bind_cancellation_slot( - slot, asio::bind_executor( - handler_executor, [handler = std::move(handler)]( - const std::exception_ptr& ep, - std::tuple result) mutable { - if (ep) - std::move(handler)(code(ep), T{}...); - else - std::apply(std::move(handler), std::move(result)); - }))); + slot, asio::bind_executor(handler_executor, [handler = std::move(handler)]( + const std::exception_ptr& ep, + results result) mutable { + if (ep) + std::move(handler)(code(ep), T{}...); + else + std::apply(std::move(handler), std::move(result)); + }))); } // ------------------------------------------------------------------------------------------------- @@ -437,7 +452,7 @@ class Event * Waits for the event to be set: auto [ec] = co_await event.wait();. The wait can be * cancelled, and then completes with \c errors::canceled. */ - Task> wait() + IoTask<> wait() { if (set_) co_return error_code{}; diff --git a/include/anyhttp/client.hpp b/include/anyhttp/client.hpp index 927c971..ade69e7 100644 --- a/include/anyhttp/client.hpp +++ b/include/anyhttp/client.hpp @@ -216,7 +216,7 @@ class Request : public Writer /// \c async_get_response() as a coroutine: /// auto [ec, response] = co_await request.get_response(); /// \return The error and the response. - Task> get_response(); + IoTask get_response(); private: #if ANYHTTP_ASIO @@ -285,7 +285,7 @@ class Client /// \c async_connect() as a coroutine: auto [ec, session] = co_await client.connect(); /// \return The error and the session. - Task> connect(); + IoTask connect(); private: #if ANYHTTP_ASIO diff --git a/include/anyhttp/client_impl.hpp b/include/anyhttp/client_impl.hpp index beceed3..dbcbc71 100644 --- a/include/anyhttp/client_impl.hpp +++ b/include/anyhttp/client_impl.hpp @@ -27,7 +27,7 @@ class Request::Impl : public Writer::Impl virtual void async_get_response(GetResponseHandler&& handler) = 0; /// async_get_response() as a coroutine, see Reader::Impl::read_some(). - virtual Task> get_response() + virtual IoTask get_response() { co_return co_await initiate( [this](GetResponseHandler handler) { async_get_response(std::move(handler)); }); diff --git a/include/anyhttp/corosio/net.hpp b/include/anyhttp/corosio/net.hpp index 7185081..5bb994d 100644 --- a/include/anyhttp/corosio/net.hpp +++ b/include/anyhttp/corosio/net.hpp @@ -263,11 +263,9 @@ std::pair buffer_sizes(TcpSocket& socket); void set_buffer_sizes(TcpSocket& socket, int send, int receive); -Task>> resolve(Executor executor, std::string host, - std::string port); +IoTask> resolve(Executor executor, std::string host, std::string port); -Task> connect(TcpSocket& socket, - std::vector endpoints); +IoTask connect(TcpSocket& socket, std::vector endpoints); inline TlsStream make_tls_stream(TcpSocket&& socket, TlsContext& context) { @@ -320,7 +318,7 @@ class Signal void notify() { waker_.wake(); } - Task> wait() + IoTask<> wait() { auto [ec] = co_await waker_.wait(); co_return std::tuple{ec}; diff --git a/include/anyhttp/corosio/runtime.hpp b/include/anyhttp/corosio/runtime.hpp index d9aef9f..851a173 100644 --- a/include/anyhttp/corosio/runtime.hpp +++ b/include/anyhttp/corosio/runtime.hpp @@ -96,6 +96,11 @@ using UdpEndpoint = corosio::endpoint; using std::error_code; +using capy::results; + +template +using IoTask = Task>; + // ================================================================================================= /// Portable error conditions, see asio/runtime.hpp. @@ -140,7 +145,7 @@ struct CompletionState { capy::continuation continuation; const capy::await_context* env = nullptr; - std::optional> result; + std::optional> result; std::optional>> on_stop; bool starting = false; // the operation is being started, see Initiation::await_suspend() @@ -162,7 +167,7 @@ template struct CompletionTraits { using State = CompletionState; - using Result = std::tuple; + using Result = results; }; } // namespace detail @@ -306,7 +311,7 @@ class Initiation private: template - void canceled(std::type_identity>) + void canceled(std::type_identity>) { state_.result.emplace(errors::canceled, Args{}...); } @@ -345,7 +350,7 @@ inline void launch(const Executor& executor, Task task, F&& on_done) * so cancelling the caller cancels the task. */ template -inline void launch(const Executor& executor, Task> task, +inline void launch(const Executor& executor, IoTask task, Completion&& handler) { std::stop_token token; @@ -355,7 +360,7 @@ inline void launch(const Executor& executor, Task> auto shared = std::make_shared>(std::move(handler)); capy::run_async( executor, token, - [shared](std::tuple result) noexcept { + [shared](results result) noexcept { std::apply(std::move(*shared), std::move(result)); }, [shared](std::exception_ptr ep) noexcept { std::move (*shared)(code(ep), T{}...); })( @@ -463,7 +468,7 @@ using value_t = std::conditional_t, std::monostate, T>; * keeping its value. What it throws passes through. */ template -capy::task> as_io(Task task, std::optional>& result) +capy::task> as_io(Task task, std::optional>& result) { if constexpr (std::is_void_v) { @@ -472,7 +477,7 @@ capy::task> as_io(Task task, std::optional>& resul } else result.emplace(co_await std::move(task)); - co_return capy::results<>{}; + co_return results<>{}; } } // namespace detail @@ -615,7 +620,7 @@ auto write(Stream& stream, const ConstBufferSequence& buffers) /// Peeks at what has arrived on \p socket, without taking it. corosio has no flags for its /// receive operations, so this waits for readiness and peeks at the native socket. template -Task> peek(Socket& socket, asio::mutable_buffer buffer) +IoTask peek(Socket& socket, asio::mutable_buffer buffer) { for (;;) { @@ -636,7 +641,7 @@ Task> peek(Socket& socket, asio::mutable_buffer b /// read_some(): corosio's datagram operations do not copy their buffer sequence, as its stream /// operations do, but point to it, so it has to outlive the operation. template -Task> receive(Socket& socket, asio::mutable_buffer buffer) +IoTask receive(Socket& socket, asio::mutable_buffer buffer) { const capy::mutable_buffer buffers(buffer.data(), buffer.size()); auto [ec, n] = co_await socket.recv(buffers); diff --git a/include/anyhttp/detail/detect.hpp b/include/anyhttp/detail/detect.hpp index 1fb6a37..308ca87 100644 --- a/include/anyhttp/detail/detect.hpp +++ b/include/anyhttp/detail/detect.hpp @@ -61,7 +61,7 @@ enum class Detected * \p buffer: the session that takes over the connection starts with what is in there. */ template -Task> detect(Socket& socket, DynamicBuffer& buffer) +IoTask detect(Socket& socket, DynamicBuffer& buffer) { std::array first; auto [ec, n] = co_await io::peek(socket, asio::buffer(first)); diff --git a/include/anyhttp/h1/io.hpp b/include/anyhttp/h1/io.hpp index e185fa4..5c53fa5 100644 --- a/include/anyhttp/h1/io.hpp +++ b/include/anyhttp/h1/io.hpp @@ -62,8 +62,8 @@ inline error_code to_error_code(const boost::system::error_code& ec) * lost. */ template -Task> read_some(Stream& stream, DynamicBuffer& buffer, - http::basic_parser& parser) +IoTask read_some(Stream& stream, DynamicBuffer& buffer, + http::basic_parser& parser) { size_t parsed = 0; boost::system::error_code ec; @@ -110,8 +110,8 @@ Task> read_some(Stream& stream, DynamicBuffer& bu /// Reads until the parser has the whole header, as Beast's \c http::async_read_header(). template -Task> read_header(Stream& stream, DynamicBuffer& buffer, - http::basic_parser& parser) +IoTask read_header(Stream& stream, DynamicBuffer& buffer, + http::basic_parser& parser) { size_t parsed = 0; while (!parser.is_header_done()) @@ -127,8 +127,7 @@ Task> read_header(Stream& stream, DynamicBuffer& /// Reads until the parser has the whole message, as Beast's \c http::async_read() with a parser. template -Task> read(Stream& stream, DynamicBuffer& buffer, - http::basic_parser& parser) +IoTask read(Stream& stream, DynamicBuffer& buffer, http::basic_parser& parser) { size_t parsed = 0; while (!parser.is_done()) @@ -144,9 +143,8 @@ Task> read(Stream& stream, DynamicBuffer& buffer, /// Reads a whole message into \p message, as Beast's \c http::async_read() with a message. template -Task> -read_message(Stream& stream, DynamicBuffer& buffer, - http::message>& message) +IoTask read_message(Stream& stream, DynamicBuffer& buffer, + http::message>& message) { http::parser parser; parser.eager(true); @@ -166,8 +164,7 @@ read_message(Stream& stream, DynamicBuffer& buffer, * but \c http::error::need_buffer, which is passed on here. */ template -Task> -write_some(Stream& stream, http::serializer& serializer) +IoTask write_some(Stream& stream, http::serializer& serializer) { if (serializer.is_done()) co_return std::tuple{error_code{}, size_t{0}}; @@ -197,8 +194,7 @@ write_some(Stream& stream, http::serializer& serializer * here, this also stops when a \c buffer_body has been written that is not the last. */ template -Task> write_until(Stream& stream, Serializer& serializer, - Predicate done) +IoTask write_until(Stream& stream, Serializer& serializer, Predicate done) { size_t written = 0; while (!done(serializer)) @@ -216,7 +212,7 @@ Task> write_until(Stream& stream, Serializer& ser /// Writes the header, as Beast's \c http::async_write_header(). template -Task> write_header(Stream& stream, Serializer& serializer) +IoTask write_header(Stream& stream, Serializer& serializer) { serializer.split(true); return h1::write_until(stream, serializer, [](auto& sr) { return sr.is_header_done(); }); @@ -224,15 +220,14 @@ Task> write_header(Stream& stream, Serializer& se /// Writes all there is, as Beast's \c http::async_write() with a serializer. template -Task> write(Stream& stream, Serializer& serializer) +IoTask write(Stream& stream, Serializer& serializer) { return h1::write_until(stream, serializer, [](auto& sr) { return sr.is_done(); }); } /// Writes all of \p message, as Beast's \c http::async_write() with a message. template -Task> write_message(Stream& stream, - http::message& message) +IoTask write_message(Stream& stream, http::message& message) { http::serializer serializer{message}; co_return co_await h1::write(stream, serializer); diff --git a/include/anyhttp/h1/session.hpp b/include/anyhttp/h1/session.hpp index 45cb61e..18c4a37 100644 --- a/include/anyhttp/h1/session.hpp +++ b/include/anyhttp/h1/session.hpp @@ -247,10 +247,10 @@ class ClientSession : public ClientSessionBase, public BeastSession void async_submit(SubmitHandler&& handler, std::string_view method, boost::urls::url url, const Fields& headers) override; - Task> submit(std::string method, boost::urls::url url, - Fields headers) override; + IoTask submit(std::string method, boost::urls::url url, + Fields headers) override; Task do_session(Buffer&& data) override; - Task> shutdown() override; + IoTask<> shutdown() override; // ---------------------------------------------------------------------------------------------- diff --git a/include/anyhttp/h2/session.hpp b/include/anyhttp/h2/session.hpp index e01af9c..3ebde16 100644 --- a/include/anyhttp/h2/session.hpp +++ b/include/anyhttp/h2/session.hpp @@ -253,7 +253,7 @@ class ClientSession : public ClientReference, public NGHttp2SessionImpl ClientSession(client::Client::Impl& parent, Executor executor, Stream&& stream); Task do_session(Buffer&& data) override; - Task> shutdown() override; + IoTask<> shutdown() override; void on_alt_svc(std::string_view field_value) override { client().on_alt_svc(field_value); } }; diff --git a/include/anyhttp/h2/session_details.hpp b/include/anyhttp/h2/session_details.hpp index 7b11eb2..61b87ae 100644 --- a/include/anyhttp/h2/session_details.hpp +++ b/include/anyhttp/h2/session_details.hpp @@ -440,7 +440,7 @@ Task ClientSession::do_session(Buffer&& buffer) // ------------------------------------------------------------------------------------------------- template -Task> ClientSession::shutdown() +IoTask<> ClientSession::shutdown() { if (std::exchange(this->shutdown_started_, true) || this->session_done_.is_set()) co_return std::tuple{error_code{}}; diff --git a/include/anyhttp/h2v2/session.hpp b/include/anyhttp/h2v2/session.hpp index df1c7de..d413044 100644 --- a/include/anyhttp/h2v2/session.hpp +++ b/include/anyhttp/h2v2/session.hpp @@ -274,7 +274,7 @@ class ClientSession : public ClientReference, public NGHttp2SessionImpl ClientSession(client::Client::Impl& parent, Executor executor, Stream&& stream); Task do_session(Buffer&& data) override; - Task> shutdown() override; + IoTask<> shutdown() override; void on_alt_svc(std::string_view field_value) override { client().on_alt_svc(field_value); } }; diff --git a/include/anyhttp/h2v2/session_details.hpp b/include/anyhttp/h2v2/session_details.hpp index d7dd119..43a7a29 100644 --- a/include/anyhttp/h2v2/session_details.hpp +++ b/include/anyhttp/h2v2/session_details.hpp @@ -369,7 +369,7 @@ Task ClientSession::do_session(Buffer&& buffer) // ------------------------------------------------------------------------------------------------- template -Task> ClientSession::shutdown() +IoTask<> ClientSession::shutdown() { if (std::exchange(this->shutdown_started_, true) || this->session_done_.is_set()) co_return std::tuple{error_code{}}; diff --git a/include/anyhttp/quic.hpp b/include/anyhttp/quic.hpp index c0f9ddc..6068902 100644 --- a/include/anyhttp/quic.hpp +++ b/include/anyhttp/quic.hpp @@ -211,7 +211,7 @@ class Connection * * \return The error and the stream. */ - std::tuple open_stream(); + results open_stream(); #if ANYHTTP_ASIO /// Waits for the next stream the peer opens. Fails with \c errors::connection_aborted once the @@ -233,7 +233,7 @@ class Connection /// \c async_accept_stream() as a coroutine: auto [ec, stream] = co_await /// connection.accept_stream(); /// \return The error and the stream. - Task> accept_stream(); + IoTask accept_stream(); /// Closes the connection: CONNECTION_CLOSE with the application error \p code. What is still /// open fails with \c errc::connection_reset. @@ -266,8 +266,7 @@ using ConnectHandler = Completion; /// \param port The port of the server. /// \param config The protocol, and how to speak it. /// \return The error and the connection. -Task> connect(Executor executor, std::string host, - std::string port, Config config); +IoTask connect(Executor executor, std::string host, std::string port, Config config); #if ANYHTTP_ASIO /// \cond internal diff --git a/include/anyhttp/quic/streams.hpp b/include/anyhttp/quic/streams.hpp index aa3095e..4f6abed 100644 --- a/include/anyhttp/quic/streams.hpp +++ b/include/anyhttp/quic/streams.hpp @@ -90,7 +90,7 @@ class StreamApplication final : public QuicApplication // // What Connection offers. // - std::tuple open_stream(); + results open_stream(); void async_accept_stream(AcceptStreamHandler&& handler); /// Puts `stream` at the end of the write queue, unless it is in there already. diff --git a/include/anyhttp/reader.hpp b/include/anyhttp/reader.hpp index 21bd17b..d4a95d7 100644 --- a/include/anyhttp/reader.hpp +++ b/include/anyhttp/reader.hpp @@ -134,7 +134,7 @@ class Reader /// Reads a part of the incoming body: auto [ec, n] = co_await reader.read_some(buffer); /// \param buffer Where to put what has been read. /// \return The error, and how many bytes have been read. - Task> read_some(asio::mutable_buffer buffer); + IoTask read_some(asio::mutable_buffer buffer); /// \overload /// @@ -145,7 +145,7 @@ class Reader template requires(asio::is_mutable_buffer_sequence::value && !std::convertible_to) - Task> read_some(const Buffers& buffers) + IoTask read_some(const Buffers& buffers) { for (auto& buffer : buffers) if (buffer.size() > 0) @@ -158,7 +158,7 @@ class Reader /// /// For capy's buffer sequences, which is what makes a Reader a \c capy::ReadStream. template - Task> read_some(Buffers buffers) + IoTask read_some(Buffers buffers) { for (auto it = capy::buffer_sequence_begin(buffers); it != capy::buffer_sequence_end(buffers); ++it) diff --git a/include/anyhttp/reader_impl.hpp b/include/anyhttp/reader_impl.hpp index 58ed602..55a456d 100644 --- a/include/anyhttp/reader_impl.hpp +++ b/include/anyhttp/reader_impl.hpp @@ -49,7 +49,7 @@ class Reader::Impl : public std::enable_shared_from_this // handler (HTTP/2, HTTP/3) makes the coroutine cheap, through initiate(). Doing the I/O in a // coroutine (HTTP/1.1) makes the handler shape cost a spawn, through launch(). // - virtual Task> read_some(asio::mutable_buffer buffer) + virtual IoTask read_some(asio::mutable_buffer buffer) { co_return co_await initiate([this, buffer](ReadSomeHandler handler) { // async_read_some(buffer, std::move(handler)); diff --git a/include/anyhttp/runtime.hpp b/include/anyhttp/runtime.hpp index cce563b..b7d9c03 100644 --- a/include/anyhttp/runtime.hpp +++ b/include/anyhttp/runtime.hpp @@ -9,7 +9,8 @@ // public API front-ends and the test fixtures test ANYHTTP_ASIO or ANYHTTP_COROSIO: the protocol // backends are written once, against these names. // -// Task, Executor, error_code, Completion, system_error +// Task, IoTask, results, Executor, error_code, Completion, +// system_error // IpAddress, TcpEndpoint, UdpEndpoint -- the runtime's own // errc (portable error conditions), errors:: (the codes of the API contract), throw_error() // last_error() -- errno, as an error_code diff --git a/include/anyhttp/server.hpp b/include/anyhttp/server.hpp index cfaeeca..3077ef0 100644 --- a/include/anyhttp/server.hpp +++ b/include/anyhttp/server.hpp @@ -268,7 +268,7 @@ class Response : public Writer /// \param status_code The status code of the response. /// \param headers The header fields of the response. /// \return The error. - Task> submit(unsigned int status_code, Fields headers); + IoTask<> submit(unsigned int status_code, Fields headers); private: #if ANYHTTP_ASIO diff --git a/include/anyhttp/server_impl.hpp b/include/anyhttp/server_impl.hpp index be30dff..73dd852 100644 --- a/include/anyhttp/server_impl.hpp +++ b/include/anyhttp/server_impl.hpp @@ -46,7 +46,7 @@ class Response::Impl : public Writer::Impl const Fields& fields) = 0; /// async_submit() as a coroutine, see Reader::Impl::read_some(). - virtual Task> submit(unsigned int status_code, Fields fields) + virtual IoTask<> submit(unsigned int status_code, Fields fields) { co_return co_await initiate( [&](StatusHandler handler) { async_submit(std::move(handler), status_code, fields); }); diff --git a/include/anyhttp/session.hpp b/include/anyhttp/session.hpp index 24010d7..3ba7456 100644 --- a/include/anyhttp/session.hpp +++ b/include/anyhttp/session.hpp @@ -238,8 +238,7 @@ class Session /// \param target The URL of the request. /// \param headers The header fields of the request. /// \return The error and the request. - Task> submit(boost::urls::url target, - Fields headers = {}); + IoTask submit(boost::urls::url target, Fields headers = {}); /// \c async_submit() with a method, as a coroutine: /// auto [ec, request] = co_await session.submit("GET", url, headers); @@ -247,18 +246,18 @@ class Session /// \param target The URL of the request. /// \param headers The header fields of the request. /// \return The error and the request. - Task> - submit(std::string_view method, boost::urls::url target, Fields headers = {}); + IoTask submit(std::string_view method, boost::urls::url target, + Fields headers = {}); /// \c async_get() as a coroutine: auto [ec, message] = co_await session.get(url); /// \param url The URL of the request. /// \param headers The header fields of the request. /// \return The error and the whole response. - Task> get(boost::urls::url url, Fields headers = {}); + IoTask get(boost::urls::url url, Fields headers = {}); /// \c async_shutdown() as a coroutine: auto [ec] = co_await session.shutdown(); /// \return The error. - Task> shutdown(); + IoTask<> shutdown(); /** * The protocol this session speaks, which over TLS the server has a say in: see diff --git a/include/anyhttp/session_impl.hpp b/include/anyhttp/session_impl.hpp index 2a26901..b39865b 100644 --- a/include/anyhttp/session_impl.hpp +++ b/include/anyhttp/session_impl.hpp @@ -23,15 +23,14 @@ class Session::Impl : public std::enable_shared_from_this const Fields& headers) = 0; /// async_submit() as a coroutine, see Reader::Impl::read_some(). - virtual Task> - submit(std::string method, boost::urls::url url, Fields headers) + virtual IoTask submit(std::string method, boost::urls::url url, Fields headers) { co_return co_await initiate([&](SubmitHandler handler) { async_submit(std::move(handler), method, std::move(url), headers); }); } /// See Session::async_shutdown(). Only a client's session has that, a server's ends by itself. - virtual Task> shutdown() + virtual IoTask<> shutdown() { co_return std::tuple{make_error_code(errc::operation_not_supported)}; } diff --git a/include/anyhttp/writer.hpp b/include/anyhttp/writer.hpp index c0e0d84..478a00c 100644 --- a/include/anyhttp/writer.hpp +++ b/include/anyhttp/writer.hpp @@ -131,13 +131,13 @@ class Writer /// Writes \p buffer as part of the body: auto [ec] = co_await writer.write(buffer); /// \param buffer What to write. /// \return The error. - Task> write(asio::const_buffer buffer); + IoTask<> write(asio::const_buffer buffer); /// Writes \p buffer, if any, and ends the body: auto [ec] = co_await /// writer.write_eof(); /// \param buffer What to write. /// \return The error. - Task> write_eof(asio::const_buffer buffer = {}); + IoTask<> write_eof(asio::const_buffer buffer = {}); #if ANYHTTP_COROSIO /** @@ -145,7 +145,7 @@ class Writer * this is what makes a Writer a \c capy::WriteStream. The body stays open, as with write(). */ template - Task> write_some(Buffers buffers) + IoTask write_some(Buffers buffers) { for (auto it = capy::buffer_sequence_begin(buffers); it != capy::buffer_sequence_end(buffers); ++it) diff --git a/include/anyhttp/writer_impl.hpp b/include/anyhttp/writer_impl.hpp index 78858f8..c75349f 100644 --- a/include/anyhttp/writer_impl.hpp +++ b/include/anyhttp/writer_impl.hpp @@ -46,7 +46,7 @@ class Writer::Impl : public std::enable_shared_from_this virtual void async_write(WriteHandler&& handler, asio::const_buffer buffer, bool eof) = 0; /// async_write() as a coroutine, see Reader::Impl::read_some(). - virtual Task> write(asio::const_buffer buffer, bool eof) + virtual IoTask<> write(asio::const_buffer buffer, bool eof) { co_return co_await initiate([this, buffer, eof](WriteHandler handler) { // async_write(std::move(handler), buffer, eof); diff --git a/src/asio/net.cpp b/src/asio/net.cpp index e569dce..21de371 100644 --- a/src/asio/net.cpp +++ b/src/asio/net.cpp @@ -149,8 +149,7 @@ void set_buffer_sizes(TcpSocket& socket, int send, int receive) socket.set_option(asio::socket_base::receive_buffer_size(receive), ec); } -Task>> resolve(Executor executor, std::string host, - std::string port) +IoTask> resolve(Executor executor, std::string host, std::string port) { asio::ip::tcp::resolver resolver(executor); auto [ec, results] = co_await resolver.async_resolve( @@ -162,8 +161,7 @@ Task>> resolve(Executor executor co_return std::tuple{ec, std::move(endpoints)}; } -Task> connect(TcpSocket& socket, - std::vector endpoints) +IoTask connect(TcpSocket& socket, std::vector endpoints) { co_return co_await asio::async_connect(socket, endpoints, asio::as_tuple); } diff --git a/src/client.cpp b/src/client.cpp index 21f7beb..1804ffc 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -39,15 +39,9 @@ Request::~Request() { reset(); } Request::Impl& Request::pimpl() const noexcept { return static_cast(Writer::pimpl()); } -static Task> no_request() -{ - co_return std::tuple{errors::bad_descriptor, Response{}}; -} +static IoTask no_request() { co_return std::tuple{errors::bad_descriptor, Response{}}; } -Task> Request::get_response() -{ - return *this ? pimpl().get_response() : no_request(); -} +IoTask Request::get_response() { return *this ? pimpl().get_response() : no_request(); } #if ANYHTTP_ASIO void Request::async_get_response_any(Request::GetResponseHandler&& handler) @@ -105,7 +99,7 @@ Client::~Client() = default; // ------------------------------------------------------------------------------------------------- -Task> Client::connect() +IoTask Client::connect() { try { diff --git a/src/corosio/net.cpp b/src/corosio/net.cpp index 8d7500c..518daac 100644 --- a/src/corosio/net.cpp +++ b/src/corosio/net.cpp @@ -105,8 +105,7 @@ void set_buffer_sizes(TcpSocket& socket, int send, int receive) ::setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVBUF, &receive, sizeof(receive)); } -Task>> resolve(Executor executor, std::string host, - std::string port) +IoTask> resolve(Executor executor, std::string host, std::string port) { corosio::resolver resolver(executor.context()); auto [ec, results] = @@ -118,8 +117,7 @@ Task>> resolve(Executor executor co_return std::tuple{ec, std::move(endpoints)}; } -Task> connect(TcpSocket& socket, - std::vector endpoints) +IoTask connect(TcpSocket& socket, std::vector endpoints) { error_code ec = make_error_code(errc::host_unreachable); for (const auto& endpoint : endpoints) diff --git a/src/h1/session.cpp b/src/h1/session.cpp index 5475c76..ccea1fa 100644 --- a/src/h1/session.cpp +++ b/src/h1/session.cpp @@ -148,7 +148,7 @@ class BeastReader : public Interface } /// Reads until something of the body has arrived in \p body_buffer, or the body has ended. - Task> read_some(asio::mutable_buffer body_buffer) override + IoTask read_some(asio::mutable_buffer body_buffer) override { assert(!reading); @@ -314,7 +314,7 @@ class WriterBase : public Parent launch(get_executor(), write(buffer, eof), std::move(handler)); } - Task> write(asio::const_buffer buffer, bool eof) override + IoTask<> write(asio::const_buffer buffer, bool eof) override { const bool empty = buffer.size() == 0; @@ -459,7 +459,7 @@ class WriterBase : public Parent // ---------------------------------------------------------------------------------------------- /// Writes the header of the message, see submit_headers(). - Task> write_head() + IoTask<> write_head() { error_code ec; try @@ -548,7 +548,7 @@ class ResponseWriter launch(super::get_executor(), submit(status_code, headers), std::move(handler)); } - Task> submit(unsigned int status_code, Fields headers) override + IoTask<> submit(unsigned int status_code, Fields headers) override { if (!session) { @@ -687,7 +687,7 @@ class RequestWriter launch(get_executor(), submit_head(headers), std::move(handler)); } - Task> submit_head(Fields headers) + IoTask<> submit_head(Fields headers) { if (!session) { @@ -706,7 +706,7 @@ class RequestWriter launch(get_executor(), get_response(), std::move(handler)); } - Task> get_response() override + IoTask get_response() override { mlogd("async_get_response:"); @@ -1248,7 +1248,7 @@ Task ClientSession::do_session(Buffer&& buffer) // ------------------------------------------------------------------------------------------------- template -Task> ClientSession::shutdown() +IoTask<> ClientSession::shutdown() { if (std::exchange(shutting_down_, true)) co_return std::tuple{error_code{}}; @@ -1301,8 +1301,8 @@ void ClientSession::async_submit(SubmitHandler&& handler, std::string_vi } template -Task> -ClientSession::submit(std::string method, boost::urls::url target, Fields headers) +IoTask ClientSession::submit(std::string method, boost::urls::url target, + Fields headers) { // // Only one request can be incomplete at a time, see ClientSession. Instead of waiting for the diff --git a/src/quic/client.cpp b/src/quic/client.cpp index 0d632b0..9b91c12 100644 --- a/src/quic/client.cpp +++ b/src/quic/client.cpp @@ -124,7 +124,7 @@ class QuicClientSession : public QuicSession const Fields& headers) override; Task do_session(Buffer&& data) override; void destroy() noexcept override; - Task> shutdown() override; + IoTask<> shutdown() override; // // Connect-time setup, with the application `factory` makes. Returns 0 on success. @@ -304,7 +304,7 @@ Task QuicClientSession::do_session(Buffer&&) void QuicClientSession::destroy() noexcept { close(); } -Task> QuicClientSession::shutdown() +IoTask<> QuicClientSession::shutdown() { // // Nothing to wait for: CONNECTION_CLOSE is all there is to an orderly end, and no answer to it diff --git a/src/quic/connection.cpp b/src/quic/connection.cpp index 4e12847..47e946b 100644 --- a/src/quic/connection.cpp +++ b/src/quic/connection.cpp @@ -126,7 +126,7 @@ std::string Connection::log_prefix() const return impl_ ? impl_->session->log_prefix() : std::string{}; } -std::tuple Connection::open_stream() +results Connection::open_stream() { if (!impl_) return {errors::bad_descriptor, Stream{}}; @@ -143,7 +143,7 @@ void Connection::async_accept_stream_any(AcceptStreamHandler&& handler) impl_->application.async_accept_stream(std::move(handler)); } -Task> Connection::accept_stream() +IoTask Connection::accept_stream() { co_return co_await initiate([this](AcceptStreamHandler handler) { // async_accept_stream_any(std::move(handler)); @@ -162,8 +162,7 @@ void Connection::reset() noexcept { impl_.reset(); } // connect() // ================================================================================================= -Task> connect(Executor executor, std::string host, - std::string port, Config config) +IoTask connect(Executor executor, std::string host, std::string port, Config config) { if (config.alpn.empty() || config.alpn.size() > 255) co_return std::tuple{make_error_code(errc::invalid_argument), Connection{}}; diff --git a/src/quic/streams.cpp b/src/quic/streams.cpp index 424358a..86db5bc 100644 --- a/src/quic/streams.cpp +++ b/src/quic/streams.cpp @@ -202,7 +202,7 @@ void StreamApplication::async_accept_stream(AcceptStreamHandler&& handler) accept_handler_ = std::move(handler); } -std::tuple StreamApplication::open_stream() +results StreamApplication::open_stream() { if (closed_ec_ || session_.closed() || !session_.application_ready()) return {errors::connection_aborted, Stream{}}; diff --git a/src/reader.cpp b/src/reader.cpp index 12a43c7..b8199fe 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -37,12 +37,9 @@ std::optional Reader::content_length() const noexcept return impl_ ? impl_->content_length() : std::nullopt; } -static Task> no_reader() -{ - co_return std::tuple{errors::bad_descriptor, size_t{0}}; -} +static IoTask no_reader() { co_return std::tuple{errors::bad_descriptor, size_t{0}}; } -Task> Reader::read_some(asio::mutable_buffer buffer) +IoTask Reader::read_some(asio::mutable_buffer buffer) { return impl_ ? impl_->read_some(buffer) : no_reader(); } diff --git a/src/server.cpp b/src/server.cpp index c9e1fca..219a59c 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -63,7 +63,7 @@ Response::~Response() { reset(); } Response::Impl& Response::pimpl() const noexcept { return static_cast(Writer::pimpl()); } -Task> Response::submit(unsigned int status_code, Fields headers) +IoTask<> Response::submit(unsigned int status_code, Fields headers) { return pimpl().submit(status_code, std::move(headers)); } diff --git a/src/session.cpp b/src/session.cpp index 06122ed..8aaaac4 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -96,8 +96,8 @@ namespace // The implementation is held by shared_ptr: a Session released while the GET is still in flight // must not pull the ground out from under the operations still running on it. // -Task> get_message(std::shared_ptr session, - boost::urls::url url, Fields headers) +IoTask get_message(std::shared_ptr session, boost::urls::url url, + Fields headers) { // // What an error leaves behind: an empty message that says status::unknown, rather than the 200 @@ -149,14 +149,14 @@ namespace { // The implementation is held by shared_ptr for the same reason as in get_message(). -Task> shutdown_session(std::shared_ptr session) +IoTask<> shutdown_session(std::shared_ptr session) { co_return co_await session->shutdown(); } } // namespace -Task> Session::shutdown() { return shutdown_session(impl); } +IoTask<> Session::shutdown() { return shutdown_session(impl); } #if ANYHTTP_ASIO void Session::async_shutdown_any(SessionShutdownHandler&& handler) @@ -165,19 +165,18 @@ void Session::async_shutdown_any(SessionShutdownHandler&& handler) } #endif -Task> Session::submit(boost::urls::url target, - Fields headers) +IoTask Session::submit(boost::urls::url target, Fields headers) { return impl->submit("POST", std::move(target), std::move(headers)); } -Task> -Session::submit(std::string_view method, boost::urls::url target, Fields headers) +IoTask Session::submit(std::string_view method, boost::urls::url target, + Fields headers) { return impl->submit(std::string(method), std::move(target), framed(method, std::move(headers))); } -Task> Session::get(boost::urls::url url, Fields headers) +IoTask Session::get(boost::urls::url url, Fields headers) { #if ANYHTTP_COROSIO // diff --git a/src/writer.cpp b/src/writer.cpp index 2f66ff0..1ded333 100644 --- a/src/writer.cpp +++ b/src/writer.cpp @@ -37,14 +37,14 @@ void Writer::content_length(std::optional content_length) return impl_ ? impl_->content_length(content_length) : void{}; } -static Task> no_writer() { co_return std::tuple{errors::bad_descriptor}; } +static IoTask<> no_writer() { co_return std::tuple{errors::bad_descriptor}; } -Task> Writer::write(asio::const_buffer buffer) +IoTask<> Writer::write(asio::const_buffer buffer) { return impl_ ? impl_->write(buffer, false) : no_writer(); } -Task> Writer::write_eof(asio::const_buffer buffer) +IoTask<> Writer::write_eof(asio::const_buffer buffer) { return impl_ ? impl_->write(buffer, true) : no_writer(); } diff --git a/test/test_connection_end.cpp b/test/test_connection_end.cpp index 1fb9c4b..f858c76 100644 --- a/test/test_connection_end.cpp +++ b/test/test_connection_end.cpp @@ -329,7 +329,7 @@ class ConnectionEndBase : public Server * an ASIO socket read once more after its end (see above), must fail a test, not hang it. */ template - Task> read_some(S& stream, asio::mutable_buffer buffer) + IoTask read_some(S& stream, asio::mutable_buffer buffer) { Timer timer(context.get_executor()); timer.arm(timeout, [&stream] { io::cancel(stream); }); diff --git a/test/test_corosio.cpp b/test/test_corosio.cpp index 65a3b6a..29be488 100644 --- a/test/test_corosio.cpp +++ b/test/test_corosio.cpp @@ -159,10 +159,10 @@ TEST(Corosio, BeastParserAndSerializer) // ------------------------------------------------------------------------------------------------- // -// 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. +// 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. // TEST(Corosio, ErrorCodeInterop) { @@ -232,7 +232,7 @@ TEST(CorosioRuntime, WHEN_parked_operation_is_completed_THEN_caller_resumes_with { corosio::io_context context; Parked parked; - std::optional> result; + std::optional> result; auto reader = [](Parked& parked, auto& result) -> Task { result = co_await parked.read(); @@ -251,7 +251,7 @@ TEST(CorosioRuntime, WHEN_caller_is_stopped_THEN_parked_operation_is_cancelled) corosio::io_context context; Parked parked; std::stop_source stop; - std::optional> result; + std::optional> result; auto reader = [](Parked& parked, auto& result) -> Task { result = co_await parked.read(); @@ -269,7 +269,7 @@ TEST(CorosioRuntime, WHEN_completion_is_dropped_THEN_caller_resumes_cancelled) { corosio::io_context context; Parked parked; - std::optional> result; + std::optional> result; auto reader = [](Parked& parked, auto& result) -> Task { result = co_await parked.read(); @@ -285,9 +285,9 @@ TEST(CorosioRuntime, WHEN_completion_is_dropped_THEN_caller_resumes_cancelled) TEST(CorosioRuntime, WHEN_task_is_launched_for_a_completion_THEN_its_result_completes_it) { corosio::io_context context; - std::optional> result; + std::optional> result; - auto task = []() -> Task> { + auto task = []() -> IoTask { co_await yield_now(); co_return std::tuple{errors::eof, size_t{7}}; }; @@ -374,7 +374,7 @@ TEST(CorosioRuntime, WHEN_context_goes_away_with_a_task_launched_for_a_completio int destroyed = 0, resumed = 0; { corosio::io_context context; - auto task = [&]() -> Task> { + auto task = [&]() -> IoTask { FrameProbe probe{destroyed}; co_await yield_now(); ++resumed; diff --git a/test/test_fixtures.hpp b/test/test_fixtures.hpp index 30a4a90..701fdb3 100644 --- a/test/test_fixtures.hpp +++ b/test/test_fixtures.hpp @@ -81,7 +81,7 @@ static void setup_logging() * auto request = check(co_await session.submit(url)); */ template -auto check(std::tuple&& result) +auto check(results&& result) { if (auto& ec = std::get<0>(result)) throw_error(ec); diff --git a/test/test_quic.cpp b/test/test_quic.cpp index 51b49a2..cce94cd 100644 --- a/test/test_quic.cpp +++ b/test/test_quic.cpp @@ -15,7 +15,7 @@ namespace /// Reads \p reader to its end, or to the first error: what it had, and how it ended (success /// for a clean end). -Task> read_all(Reader& reader) +IoTask read_all(Reader& reader) { std::string body; std::array buffer; From 544463ff8d20ba5f8c576e9df3af178606637fd4 Mon Sep 17 00:00:00 2001 From: Peter Eisenlohr Date: Fri, 6 Nov 2026 19:45:59 +0000 Subject: [PATCH 2/3] docs: one runtime section for both I/O runtimes in the reference Breathe showed each type of the runtime layer as what it is on ASIO, and each code of errors:: with its ASIO initializer. The types are now declared in reference.rst without a definition, with what each is on ASIO and on COROSIO. The errors stay with Doxygen, as comments elsewhere link to them, with \hideinitializer and a \runtimes{asio, corosio} alias that renders the same two lines. Co-Authored-By: Claude Opus 5.5 --- docs/Doxyfile.in | 1 + docs/README.md | 8 +++- docs/reference.rst | 77 ++++++++++++++++++++++++++------ include/anyhttp/asio/runtime.hpp | 19 ++++++++ 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 07ec5e4..d95365b 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -30,6 +30,7 @@ EXCLUDE_SYMBOLS = Impl \ # the first sentence of a comment is its summary, there is no need to say so with \brief JAVADOC_AUTOBRIEF = YES +ALIASES = "runtimes{2}=\verbatim embed:rst^^:ASIO: ``\1``^^:COROSIO: ``\2``^^\endverbatim" # 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 diff --git a/docs/README.md b/docs/README.md index 96642b7..4e7a799 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,7 +32,8 @@ 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 I/O runtime -and gives the same result: the comments on the types of the two runtimes are in `asio/runtime.hpp`. +and gives the same result: Doxygen reads `asio/runtime.hpp` for the runtime layer, whichever runtime +the tree is on. ## Writing it @@ -44,6 +45,11 @@ Warnings are errors in both tools, so a public symbol without a comment, a param * 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 on both I/O runtimes anyway, and `[...]`. +* The runtime layer has one section for both I/O runtimes. Its functions come from `asio/runtime.hpp` + as everything else does, and so do the codes of `errors::`, each with `\hideinitializer` and a + `\runtimes{asio, corosio}` that says what it is on each (an alias in `Doxyfile.in`). Its types, + `Task`, `IoTask`, `results`, `error_code` and the others, are declared in `reference.rst` instead, + as Breathe would show what they stand for on ASIO only; keep those in step with the headers. * 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 diff --git a/docs/reference.rst b/docs/reference.rst index f522226..684f156 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -141,26 +141,77 @@ 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. +What the interface is written in. The names are the same on both I/O runtimes, and what each +stands for is the runtime's own, given here for both, as in :ref:`the table `. -.. doxygentypedef:: anyhttp::Task +Types +^^^^^ -.. doxygentypedef:: anyhttp::Executor +.. cpp:type:: template anyhttp::Task -.. doxygentypedef:: anyhttp::IpAddress + A coroutine, as the runtime runs it. -.. doxygentypedef:: anyhttp::TcpEndpoint + :ASIO: ``asio::awaitable`` + :COROSIO: ``capy::task`` -.. doxygentypedef:: anyhttp::UdpEndpoint +.. cpp:type:: template anyhttp::IoTask -.. doxygentypedef:: anyhttp::error_code + An operation, as the coroutine that yields its :cpp:type:`results`: ``IoTask`` is + ``Task>``, ``IoTask<>`` one that reports only its error. -.. doxygentypedef:: anyhttp::results + :ASIO: ``asio::awaitable>`` + :COROSIO: ``capy::task>`` -.. doxygentypedef:: anyhttp::IoTask +.. cpp:type:: template anyhttp::results -.. doxygentypedef:: anyhttp::system_error + What an operation yields: its error first, then what it has to report besides, which + ``auto [ec, n] = co_await reader.read_some(buffer);`` takes apart. Whether that is meaningful + when there is an error is up to the operation. + + :ASIO: ``std::tuple`` + :COROSIO: ``capy::results``, which is ``std::tuple`` + +.. cpp:type:: anyhttp::error_code + + What every operation reports its outcome with. + + :ASIO: ``boost::system::error_code`` + :COROSIO: ``std::error_code`` + +.. cpp:type:: anyhttp::system_error + + What the runtime throws an :cpp:type:`error_code` as. + + :ASIO: ``boost::system::system_error`` + :COROSIO: ``std::system_error`` + +.. cpp:type:: anyhttp::Executor + + What sessions, streams and their readers and writers run on. + + :ASIO: ``asio::any_io_executor`` + :COROSIO: ``capy::any_executor`` + +.. cpp:type:: anyhttp::IpAddress + + The runtime's own IP address, which the API speaks, too. + + :ASIO: ``asio::ip::address`` + :COROSIO: ``corosio::ip_address`` + +.. cpp:type:: anyhttp::TcpEndpoint + + The runtime's own TCP endpoint. + + :ASIO: ``asio::ip::tcp::endpoint`` + :COROSIO: ``corosio::endpoint`` + +.. cpp:type:: anyhttp::UdpEndpoint + + The runtime's own UDP endpoint. + + :ASIO: ``asio::ip::udp::endpoint`` + :COROSIO: ``corosio::endpoint``, the same type as :cpp:type:`TcpEndpoint` .. doxygentypedef:: anyhttp::DefaultCompletionToken @@ -169,8 +220,8 @@ something else, which :ref:`the table ` has. Errors ^^^^^^ -Where the two runtimes spell an error differently, see :ref:`the table `. Anything -else compares against ``errc::``. +The codes anyhttp reports where the two runtimes spell them differently. Anything else compares +against ``errc::``, which is ``boost::system::errc`` on ASIO and ``std::errc`` on COROSIO. .. doxygenvariable:: anyhttp::errors::eof diff --git a/include/anyhttp/asio/runtime.hpp b/include/anyhttp/asio/runtime.hpp index ee6c7a3..970cbb1 100644 --- a/include/anyhttp/asio/runtime.hpp +++ b/include/anyhttp/asio/runtime.hpp @@ -71,6 +71,9 @@ namespace anyhttp { namespace asio = boost::asio; +// The reference declares the types from here to errc in docs/reference.rst, with what each is on +// both runtimes: from here, Breathe would show the ASIO one only. + /// A coroutine, as the runtime runs it. template using Task = asio::awaitable; @@ -123,27 +126,43 @@ namespace errc = boost::system::errc; namespace errors { /// The end of an incoming body. +/// \runtimes{asio::error::eof,capy::error::eof} +/// \hideinitializer inline const error_code eof = asio::error::eof; /// An incoming body that ended before it was complete: a reset stream, a lost connection. +/// \runtimes{beast::http::error::partial_message,capy::error::stream_truncated} +/// \hideinitializer inline const error_code partial_message = boost::beast::http::error::partial_message; /// An operation that was cancelled before it completed. +/// \runtimes{errc::operation_canceled,capy::error::canceled} +/// \hideinitializer inline const error_code canceled = make_error_code(errc::operation_canceled); /// HTTP/1.1: a request or response that has to wait for an earlier one, see Session. +/// \runtimes{asio::error::would_block,errc::operation_would_block} +/// \hideinitializer inline const error_code would_block = asio::error::would_block; /// A connection that can not carry any more requests or responses. +/// \runtimes{asio::error::connection_aborted,errc::connection_aborted} +/// \hideinitializer inline const error_code connection_aborted = asio::error::connection_aborted; /// An operation on a Reader or Writer after its implementation has been released. +/// \runtimes{asio::error::bad_descriptor,errc::bad_file_descriptor} +/// \hideinitializer inline const error_code bad_descriptor = asio::error::bad_descriptor; /// An operation that may be in progress only once at a time. +/// \runtimes{asio::error::already_started,errc::connection_already_in_progress} +/// \hideinitializer inline const error_code already_started = asio::error::already_started; /// A header section larger than Config::max_header_size. +/// \runtimes{beast::http::error::header_limit,beast::http::error::header_limit} +/// \hideinitializer inline const error_code header_limit = boost::beast::http::error::header_limit; } // namespace errors From ecc70cf476c46380f91982ca1b4b35f1aa92c9b1 Mon Sep 17 00:00:00 2001 From: Peter Eisenlohr Date: Fri, 6 Nov 2026 19:48:27 +0000 Subject: [PATCH 3/3] chore: clang-tidy cleanups, and re-enable two checks Re-enable modernize-concat-nested-namespaces and performance-avoid-endl, which had one and three findings. Disable readability-redundant-qualified-alias (aliases read better than using-declarations, in the runtime headers and in Doxygen) and performance-enum-size. Mark the intended implicit conversions NOLINT, drop unused includes and a redundant typename. Co-Authored-By: Claude Opus 5.5 --- .clang-tidy | 7 +++---- include/anyhttp/asio/runtime.hpp | 5 +---- include/anyhttp/common.hpp | 14 +++++++++----- include/anyhttp/corosio/runtime.hpp | 26 +++++++++++++++----------- include/anyhttp/quic.hpp | 2 -- include/anyhttp/server_impl.hpp | 5 +++-- src/h2/stream.cpp | 2 +- src/issues/beast_issue_3032.cpp | 6 +++--- src/net.cpp | 11 ++--------- 9 files changed, 37 insertions(+), 41 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index a5e47ca..2cf5c3f 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,9 +1,6 @@ # clang-tidy config file --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,modernize*,performance*,readability-*size*,readability*redundant*,-readability-redundant-member-init,-readability-redundant-access-specifiers,misc*,boost-use-to-string,cert*,google-explicit-constructor,-cert-err58-cpp,-modernize-concat-nested-namespaces,-modernize-use-nodiscard,-misc-non-private-member-variables-in-classes,-modernize-use-trailing-return-type,-misc-no-recursion,-misc-const-correctness,-misc-use-anonymous-namespace,-performance-avoid-endl' -# note DEV: -# disabled 'hicpp-signed-bitwise' because it's too noisy -# (IgnorePositiveIntegerLiterals doesn't seem to work, re-evaluate with Clang 11+) +Checks: 'clang-diagnostic-*,clang-analyzer-*,modernize*,performance*,readability-*size*,readability*redundant*,-readability-redundant-member-init,-readability-redundant-access-specifiers,misc*,boost-use-to-string,cert*,google-explicit-constructor,-cert-err58-cpp,-modernize-use-nodiscard,-misc-non-private-member-variables-in-classes,-modernize-use-trailing-return-type,-misc-no-recursion,-misc-const-correctness,-misc-use-anonymous-namespace,-readability-redundant-qualified-alias,-performance-enum-size' WarningsAsErrors: '' HeaderFilterRegex: '' @@ -22,4 +19,6 @@ CheckOptions: value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' + - key: performance-unnecessary-value-param.AllowedTypes + value: 'std::(__[A-Za-z0-9_]+::)*exception_ptr$' ... diff --git a/include/anyhttp/asio/runtime.hpp b/include/anyhttp/asio/runtime.hpp index 970cbb1..f6eee1b 100644 --- a/include/anyhttp/asio/runtime.hpp +++ b/include/anyhttp/asio/runtime.hpp @@ -176,10 +176,7 @@ using system_error = boost::system::system_error; /// Throws \p ec, for code that reports errors as exceptions, as the runtime's own does. /// \param ec The error. -[[noreturn]] inline void throw_error(const error_code& ec) -{ - throw boost::system::system_error(ec); -} +[[noreturn]] inline void throw_error(const error_code& ec) { throw system_error(ec); } /// \cond internal /// What `errno` holds, for code that calls the operating system itself. diff --git a/include/anyhttp/common.hpp b/include/anyhttp/common.hpp index 1eae82f..fad40a5 100644 --- a/include/anyhttp/common.hpp +++ b/include/anyhttp/common.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -16,7 +15,6 @@ #include #include #include -#include #include // ================================================================================================= @@ -81,7 +79,7 @@ class FieldValue /// \param value The value of the header field. template requires std::formattable - FieldValue(const T& value) + FieldValue(const T& value) // NOLINT(google-explicit-constructor) { if constexpr (std::convertible_to) view = value; @@ -103,7 +101,10 @@ class FieldValue /// \endcond /// \return The value, which lives as long as the FieldValue and what it refers to. - operator std::string_view() const noexcept { return view; } + operator std::string_view() const noexcept // NOLINT(google-explicit-constructor) + { + return view; + } private: std::string_view view; @@ -248,7 +249,10 @@ std::string what(const anyhttp::error_code& ec); struct HttpDate { std::array chars; - operator std::string_view() const noexcept { return {chars.data(), chars.size()}; } + operator std::string_view() const noexcept // NOLINT(google-explicit-constructor) + { + return {chars.data(), chars.size()}; + } }; /// Formats \p tp as an HTTP date, clamped to the years 0000 to 9999 the format has room for. diff --git a/include/anyhttp/corosio/runtime.hpp b/include/anyhttp/corosio/runtime.hpp index 851a173..d43e6ca 100644 --- a/include/anyhttp/corosio/runtime.hpp +++ b/include/anyhttp/corosio/runtime.hpp @@ -191,7 +191,7 @@ class Completion using State = detail::CompletionState; Completion() noexcept = default; - Completion(std::nullptr_t) noexcept {} + Completion(std::nullptr_t) noexcept {} // NOLINT(*-explicit-constructor) explicit Completion(State* state) noexcept : state_(state) {} Completion(Completion&& other) noexcept : state_(std::exchange(other.state_, nullptr)) {} Completion& operator=(Completion&& other) noexcept @@ -307,7 +307,7 @@ class Initiation return std::noop_coroutine(); } - typename Traits::Result await_resume() { return std::move(*state_.result); } + Traits::Result await_resume() { return std::move(*state_.result); } private: template @@ -317,12 +317,12 @@ class Initiation } Init init_; - typename Traits::State state_; + Traits::State state_; }; } // namespace detail -/// Starts an operation that completes a Completion, and waits for it. +/// Starts an operation that completes a \c Completion, and waits for it. template auto initiate(Init&& init) { @@ -341,8 +341,13 @@ inline void launch(const Executor& executor, Task task, F&& on_done) { auto done = std::make_shared>(std::forward(on_done)); capy::run_async( - executor, [done]() noexcept { (*done)(std::exception_ptr{}); }, - [done](std::exception_ptr ep) noexcept { (*done)(ep); })(std::move(task)); + executor, + [done]() noexcept { // + (*done)(std::exception_ptr{}); + }, + [done](std::exception_ptr ep) noexcept { // + (*done)(ep); + })(std::move(task)); } /** @@ -360,11 +365,12 @@ inline void launch(const Executor& executor, IoTask task, auto shared = std::make_shared>(std::move(handler)); capy::run_async( executor, token, - [shared](results result) noexcept { + [shared](results result) noexcept { // std::apply(std::move(*shared), std::move(result)); }, - [shared](std::exception_ptr ep) noexcept { std::move (*shared)(code(ep), T{}...); })( - std::move(task)); + [shared](std::exception_ptr ep) noexcept { // + std::move (*shared)(code(ep), T{}...); + })(std::move(task)); } // ------------------------------------------------------------------------------------------------- @@ -542,8 +548,6 @@ class Event void set() { event_.set(); } void clear() noexcept { event_.clear(); } bool is_set() const noexcept { return event_.is_set(); } - - /// auto [ec] = co_await event.wait(); auto wait() { return event_.wait(); } private: diff --git a/include/anyhttp/quic.hpp b/include/anyhttp/quic.hpp index 6068902..cab70d7 100644 --- a/include/anyhttp/quic.hpp +++ b/include/anyhttp/quic.hpp @@ -19,7 +19,6 @@ // same UDP port as HTTP/3. // -#include "common.hpp" #include "reader.hpp" #include "writer.hpp" @@ -34,7 +33,6 @@ #include #include #include -#include namespace anyhttp::quic { diff --git a/include/anyhttp/server_impl.hpp b/include/anyhttp/server_impl.hpp index 73dd852..387d0f7 100644 --- a/include/anyhttp/server_impl.hpp +++ b/include/anyhttp/server_impl.hpp @@ -48,8 +48,9 @@ class Response::Impl : public Writer::Impl /// async_submit() as a coroutine, see Reader::Impl::read_some(). virtual IoTask<> submit(unsigned int status_code, Fields fields) { - co_return co_await initiate( - [&](StatusHandler handler) { async_submit(std::move(handler), status_code, fields); }); + co_return co_await initiate([&](StatusHandler handler) { // + async_submit(std::move(handler), status_code, fields); + }); } }; diff --git a/src/h2/stream.cpp b/src/h2/stream.cpp index 6fcbe7a..0bc9cce 100644 --- a/src/h2/stream.cpp +++ b/src/h2/stream.cpp @@ -585,7 +585,7 @@ void NGHttp2Stream::on_data(const uint8_t* data, size_t len) call_read_handler(asio::const_buffer(data, len)); } -void NGHttp2Stream::on_eof(nghttp2_session* session, int32_t id_) +void NGHttp2Stream::on_eof(nghttp2_session*, int32_t id_) { assert(id == id_); assert(!eof_received); diff --git a/src/issues/beast_issue_3032.cpp b/src/issues/beast_issue_3032.cpp index 0641340..70cff51 100644 --- a/src/issues/beast_issue_3032.cpp +++ b/src/issues/beast_issue_3032.cpp @@ -76,9 +76,9 @@ static void test(bool with_nullptr) int main() { - std::cout << "--- GOOD -----------------" << std::endl; + std::cout << "--- GOOD -----------------\n"; test(true); - std::cout << "--- BAD ------------------" << std::endl; + std::cout << "--- BAD ------------------\n"; test(false); - std::cout << "--------------------------" << std::endl; + std::cout << "--------------------------\n"; } diff --git a/src/net.cpp b/src/net.cpp index bdcf8e5..547a793 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -7,14 +7,11 @@ #include -namespace anyhttp -{ - // ================================================================================================= // UDP, the same in both runtimes, see anyhttp/net.hpp. // ================================================================================================= -namespace io +namespace anyhttp::io { error_code bind(UdpSocket& socket, const UdpEndpoint& endpoint) noexcept @@ -59,8 +56,4 @@ error_code send(UdpSocket& socket, asio::const_buffer buffer) noexcept return {}; } -} // namespace io - -// ================================================================================================= - -} // namespace anyhttp +} // namespace anyhttp::io